summaryrefslogtreecommitdiff
path: root/scripts/frontend/test.js
blob: 71a8bebf0f2400d63ff5c3a5a5e219b22d7acb7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env node

const { spawn } = require('child_process');
const { EOL } = require('os');
const program = require('commander');
const chalk = require('chalk');

const SUCCESS_CODE = 0;
const JEST_ROUTE = 'spec/frontend';
const KARMA_ROUTE = 'spec/javascripts';
const COMMON_ARGS = ['--colors'];
const jestArgs = [...COMMON_ARGS, '--passWithNoTests'];
const karmaArgs = [...COMMON_ARGS, '--no-fail-on-empty-test-suite'];

program
  .usage('[options] <file ...>')
  .option('-p, --parallel', 'Run tests suites in parallel')
  .option(
    '-w, --watch',
    'Rerun tests when files change (tests will be run in parallel if this enabled)',
  )
  .parse(process.argv);

const shouldParallelize = program.parallel || program.watch;

const isSuccess = code => code === SUCCESS_CODE;

const combineExitCodes = codes => {
  const firstFail = codes.find(x => !isSuccess(x));

  return firstFail === undefined ? SUCCESS_CODE : firstFail;
};

const skipIfFail = fn => code => (isSuccess(code) ? fn() : code);

const endWithEOL = str => (str[str.length - 1] === '\n' ? str : `${str}${EOL}`);

const runTests = paths => {
  if (shouldParallelize) {
    return Promise.all([runJest(paths), runKarma(paths)]).then(combineExitCodes);
  } else {
    return runJest(paths).then(skipIfFail(() => runKarma(paths)));
  }
};

const spawnYarnScript = (cmd, args) => {
  return new Promise((resolve, reject) => {
    const proc = spawn('yarn', ['run', cmd, ...args]);
    const output = data => {
      const text = data
        .toString()
        .split(/\r?\n/g)
        .map((line, idx, { length }) =>
          idx === length - 1 && !line ? line : `${chalk.gray(cmd)}: ${line}`,
        )
        .join(EOL);

      return endWithEOL(text);
    };

    proc.stdout.on('data', data => {
      process.stdout.write(output(data));
    });

    proc.stderr.on('data', data => {
      process.stderr.write(output(data));
    });

    proc.on('close', code => {
      process.stdout.write(output(`exited with code ${code}`));

      // We resolve even on a failure code because a `reject` would cause
      // Promise.all to reject immediately (without waiting for other promises)
      // to finish.
      resolve(code);
    });
  });
};

const runJest = args => {
  return spawnYarnScript('jest', [...jestArgs, ...toJestArgs(args)]);
};

const runKarma = args => {
  return spawnYarnScript('karma', [...karmaArgs, ...toKarmaArgs(args)]);
};

const replacePath = to => path =>
  path
    .replace(JEST_ROUTE, to)
    .replace(KARMA_ROUTE, to)
    .replace('app/assets/javascripts', to);

const replacePathForJest = replacePath(JEST_ROUTE);

const replacePathForKarma = replacePath(KARMA_ROUTE);

const toJestArgs = paths => paths.map(replacePathForJest);

const toKarmaArgs = paths =>
  paths.reduce((acc, path) => acc.concat('-f', replacePathForKarma(path)), []);

const main = paths => {
  if (program.watch) {
    jestArgs.push('--watch');
    karmaArgs.push('--single-run', 'false', '--auto-watch');
  }
  runTests(paths).then(code => {
    console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
    if (isSuccess(code)) {
      console.log(chalk.bgGreen(chalk.black('All tests passed :)')));
    } else {
      console.log(chalk.bgRed(chalk.white(`Some tests failed :(`)));
    }
    console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');

    if (!isSuccess(code)) {
      process.exit(code);
    }
  });
};

main(program.args);