test: remove mocha dependency (#3576)

This commit is contained in:
Pavel Feldman 2020-08-22 00:05:24 -07:00 committed by GitHub
parent 93d8839947
commit b909924a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 213 additions and 310 deletions

View File

@ -14,14 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import Mocha from 'mocha';
import { Test, Suite } from './test'; import { Test, Suite } from './test';
import { installTransform } from './transform'; import { installTransform } from './transform';
Error.stackTraceLimit = 15; Error.stackTraceLimit = 15;
let revertBabelRequire: () => void;
function specBuilder(modifiers, specCallback) { function specBuilder(modifiers, specCallback) {
function builder(specs, last) { function builder(specs, last) {
const callable = (...args) => { const callable = (...args) => {
@ -52,16 +49,15 @@ function specBuilder(modifiers, specCallback) {
return builder({}, null); return builder({}, null);
} }
export function fixturesUI(options, mochaSuite: any) { export function fixturesUI(suite: Suite, file: string, timeout: number): () => void {
const suites = [mochaSuite.__nomocha as Suite]; const suites = [suite];
mochaSuite.on(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, function(context, file) {
const it = specBuilder(['skip', 'fail', 'slow', 'only'], (specs, title, fn) => { const it = specBuilder(['skip', 'fail', 'slow', 'only'], (specs, title, fn) => {
const suite = suites[0]; const suite = suites[0];
const test = new Test(title, fn); const test = new Test(title, fn);
test.file = file; test.file = file;
test.slow = specs.slow && specs.slow[0]; test.slow = specs.slow && specs.slow[0];
test.timeout = options.timeout; test.timeout = timeout;
const only = specs.only && specs.only[0]; const only = specs.only && specs.only[0];
if (only) if (only)
@ -70,14 +66,7 @@ export function fixturesUI(options, mochaSuite: any) {
test.pending = true; test.pending = true;
if (!only && specs.fail && specs.fail[0]) if (!only && specs.fail && specs.fail[0])
test.pending = true; test.pending = true;
test.pending = test.pending || suite.isPending(); test.pending = test.pending || suite.isPending();
if (test.pending)
fn = null;
const wrapper = fn ? options.testWrapper(test, fn) : undefined;
if (wrapper)
wrapper.toString = () => fn.toString();
test._materialize(wrapper);
suite.addTest(test); suite.addTest(test);
return test; return test;
}); });
@ -98,19 +87,16 @@ export function fixturesUI(options, mochaSuite: any) {
suites.shift(); suites.shift();
}); });
context.beforeEach = fn => options.hookWrapper(mochaSuite.beforeEach.bind(mochaSuite), fn); (global as any).beforeEach = fn => suite._addHook('beforeEach', fn);
context.afterEach = fn => options.hookWrapper(mochaSuite.afterEach.bind(mochaSuite), fn); (global as any).afterEach = fn => suite._addHook('afterEach', fn);
context.describe = describe; (global as any).beforeAll = fn => suite._addHook('beforeAll', fn);
(context as any).fdescribe = describe.only(true); (global as any).afterAll = fn => suite._addHook('afterAll', fn);
context.xdescribe = describe.skip(true); (global as any).describe = describe;
context.it = it; (global as any).fdescribe = describe.only(true);
(context as any).fit = it.only(true); (global as any).xdescribe = describe.skip(true);
context.xit = it.skip(true); (global as any).it = it;
(global as any).fit = it.only(true);
(global as any).xit = it.skip(true);
revertBabelRequire = installTransform(); return installTransform();
}); }
mochaSuite.on(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, function(context, file, mocha) {
revertBabelRequire();
});
};

View File

@ -18,15 +18,10 @@ const child_process = require('child_process');
const crypto = require('crypto'); const crypto = require('crypto');
const path = require('path'); const path = require('path');
const { EventEmitter } = require('events'); const { EventEmitter } = require('events');
const Mocha = require('mocha');
const builtinReporters = require('mocha/lib/reporters'); const builtinReporters = require('mocha/lib/reporters');
const DotRunner = require('./dotReporter'); const DotRunner = require('./dotReporter');
const { lookupRegistrations } = require('./fixtures'); const { lookupRegistrations } = require('./fixtures');
const constants = Mocha.Runner.constants;
// Mocha runner does not remove uncaughtException listeners.
process.setMaxListeners(0);
class Runner extends EventEmitter { class Runner extends EventEmitter {
constructor(suite, total, options) { constructor(suite, total, options) {
super(); super();
@ -81,12 +76,12 @@ class Runner extends EventEmitter {
} }
async run() { async run() {
this.emit(constants.EVENT_RUN_BEGIN, {}); this.emit('start', {});
this._queue = this._filesSortedByWorkerHash(); this._queue = this._filesSortedByWorkerHash();
// Loop in case job schedules more jobs // Loop in case job schedules more jobs
while (this._queue.length) while (this._queue.length)
await this._dispatchQueue(); await this._dispatchQueue();
this.emit(constants.EVENT_RUN_END, {}); this.emit('end', {});
} }
async _dispatchQueue() { async _dispatchQueue() {
@ -109,16 +104,11 @@ class Runner extends EventEmitter {
let doneCallback; let doneCallback;
const result = new Promise(f => doneCallback = f); const result = new Promise(f => doneCallback = f);
worker.once('done', params => { worker.once('done', params => {
this.stats.duration += params.stats.duration;
this.stats.failures += params.stats.failures;
this.stats.passes += params.stats.passes;
this.stats.pending += params.stats.pending;
this.stats.tests += params.stats.passes + params.stats.pending + params.stats.failures;
// When worker encounters error, we will restart it. // When worker encounters error, we will restart it.
if (params.error) { if (params.error || params.fatalError) {
this._restartWorker(worker); this._restartWorker(worker);
// If there are remaining tests, we will queue them. // If there are remaining tests, we will queue them.
if (params.remaining.length) if (params.remaining.length && !params.fatalError)
this._queue.unshift({ ...entry, ordinals: params.remaining }); this._queue.unshift({ ...entry, ordinals: params.remaining });
} else { } else {
this._workerAvailable(worker); this._workerAvailable(worker);
@ -150,17 +140,28 @@ class Runner extends EventEmitter {
_createWorker() { _createWorker() {
const worker = this._options.debug ? new InProcessWorker(this) : new OopWorker(this); const worker = this._options.debug ? new InProcessWorker(this) : new OopWorker(this);
worker.on('test', params => this.emit(constants.EVENT_TEST_BEGIN, this._updateTest(params.test))); worker.on('test', params => {
worker.on('pending', params => this.emit(constants.EVENT_TEST_PENDING, this._updateTest(params.test))); ++this.stats.tests;
worker.on('pass', params => this.emit(constants.EVENT_TEST_PASS, this._updateTest(params.test))); this.emit('test', this._updateTest(params.test));
});
worker.on('pending', params => {
++this.stats.tests;
++this.stats.pending;
this.emit('pending', this._updateTest(params.test));
});
worker.on('pass', params => {
++this.stats.passes;
this.emit('pass', this._updateTest(params.test));
});
worker.on('fail', params => { worker.on('fail', params => {
++this.stats.failures;
const out = worker.takeOut(); const out = worker.takeOut();
if (out.length) if (out.length)
params.error.stack += '\n\x1b[33mstdout: ' + out.join('\n') + '\x1b[0m'; params.error.stack += '\n\x1b[33mstdout: ' + out.join('\n') + '\x1b[0m';
const err = worker.takeErr(); const err = worker.takeErr();
if (err.length) if (err.length)
params.error.stack += '\n\x1b[33mstderr: ' + err.join('\n') + '\x1b[0m'; params.error.stack += '\n\x1b[33mstderr: ' + err.join('\n') + '\x1b[0m';
this.emit(constants.EVENT_TEST_FAIL, this._updateTest(params.test), params.error); this.emit('fail', this._updateTest(params.test), params.error);
}); });
worker.on('exit', () => { worker.on('exit', () => {
this._workers.delete(worker); this._workers.delete(worker);

View File

@ -14,10 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import Mocha from 'mocha';
import { fixturesUI } from './fixturesUI';
import { EventEmitter } from 'events';
export type Configuration = { name: string, value: string }[]; export type Configuration = { name: string, value: string }[];
export class Test { export class Test {
@ -34,18 +30,13 @@ export class Test {
_configurationObject: Configuration; _configurationObject: Configuration;
_configurationString: string; _configurationString: string;
_overriddenFn: Function; _overriddenFn: Function;
_impl: any; _startTime: number;
constructor(title: string, fn: Function) { constructor(title: string, fn: Function) {
this.title = title; this.title = title;
this.fn = fn; this.fn = fn;
} }
_materialize(overriddenFn: Function) {
this._impl = new Mocha.Test(this.title, overriddenFn);
this._impl.pending = this.pending;
}
clone(): Test { clone(): Test {
const test = new Test(this.title, this.fn); const test = new Test(this.title, this.fn);
test.suite = this.suite; test.suite = this.suite;
@ -54,7 +45,6 @@ export class Test {
test.pending = this.pending; test.pending = this.pending;
test.timeout = this.timeout; test.timeout = this.timeout;
test._overriddenFn = this._overriddenFn; test._overriddenFn = this._overriddenFn;
test._materialize(this._overriddenFn);
return test; return test;
} }
@ -80,13 +70,12 @@ export class Suite {
pending = false; pending = false;
file: string; file: string;
_impl: any; _hooks: { type: string, fn: Function } [] = [];
_entries: (Suite | Test)[] = [];
constructor(title: string, parent?: Suite) { constructor(title: string, parent?: Suite) {
this.title = title; this.title = title;
this.parent = parent; this.parent = parent;
this._impl = new Mocha.Suite(title, new Mocha.Context());
this._impl.__nomocha = this;
} }
titlePath(): string[] { titlePath(): string[] {
@ -97,7 +86,9 @@ export class Suite {
total(): number { total(): number {
let count = 0; let count = 0;
this.eachTest(fn => ++count); this.eachTest(fn => {
++count;
});
return count; return count;
} }
@ -108,20 +99,25 @@ export class Suite {
addTest(test: Test) { addTest(test: Test) {
test.suite = this; test.suite = this;
this.tests.push(test); this.tests.push(test);
this._impl.addTest(test._impl); this._entries.push(test);
} }
addSuite(suite: Suite) { addSuite(suite: Suite) {
suite.parent = this; suite.parent = this;
this.suites.push(suite); this.suites.push(suite);
this._impl.addSuite(suite._impl); this._entries.push(suite);
} }
eachTest(fn: (test: Test) => void) { eachTest(fn: (test: Test) => boolean | void): boolean {
for (const suite of this.suites) for (const suite of this.suites) {
suite.eachTest(fn); if (suite.eachTest(fn))
for (const test of this.tests) return true;
fn(test); }
for (const test of this.tests) {
if (fn(test))
return true;
}
return false;
} }
clone(): Suite { clone(): Suite {
@ -129,84 +125,29 @@ export class Suite {
suite.only = this.only; suite.only = this.only;
suite.file = this.file; suite.file = this.file;
suite.pending = this.pending; suite.pending = this.pending;
suite._impl = this._impl.clone();
return suite; return suite;
} }
}
class NullReporter { _renumber() {
stats = { let ordinal = 0;
suites: 0, this.eachTest((test: Test) => {
tests: 0, // All tests are identified with their ordinals.
passes: 0, test._ordinal = ordinal++;
pending: 0,
failures: 0
};
runner = null;
failures = [];
epilogue: () => {};
}
type NoMockaOptions = {
forbidOnly?: boolean;
timeout: number;
testWrapper: (test: Test, fn: Function) => Function;
hookWrapper: (hook: any, fn: Function) => Function;
};
class PatchedMocha extends Mocha {
suite: any;
static pendingSuite: Suite;
constructor(suite, options) {
PatchedMocha.pendingSuite = suite;
super(options);
}
grep(...args) {
this.suite = new Mocha.Suite('', new Mocha.Context());
this.suite.__nomocha = PatchedMocha.pendingSuite;
PatchedMocha.pendingSuite._impl = this.suite;
return super.grep(...args);
}
}
export class Runner extends EventEmitter {
private _mochaRunner: any;
constructor(mochaRunner: any) {
super();
const constants = Mocha.Runner.constants;
this._mochaRunner = mochaRunner;
this._mochaRunner.on(constants.EVENT_TEST_BEGIN, test => this.emit('test', test));
this._mochaRunner.on(constants.EVENT_TEST_PENDING, test => this.emit('pending', test));
this._mochaRunner.on(constants.EVENT_TEST_PASS, test => this.emit('pass', test));
this._mochaRunner.on(constants.EVENT_TEST_FAIL, (test, err) => this.emit('fail', test, err));
this._mochaRunner.on(constants.EVENT_RUN_END, () => this.emit('done'));
}
duration(): number {
return this._mochaRunner.stats.duration || 0;
}
}
export class NoMocha {
suite: Suite;
private _mocha: Mocha;
constructor(file: string, options: NoMockaOptions) {
this.suite = new Suite('');
this._mocha = new PatchedMocha(this.suite, {
forbidOnly: options.forbidOnly,
reporter: NullReporter,
timeout: options.timeout,
ui: fixturesUI.bind(null, options)
}); });
this._mocha.addFile(file);
(this._mocha as any).loadFiles();
} }
run(cb: () => void): Runner { _addHook(type: string, fn: any) {
return new Runner(this._mocha.run(cb)); this._hooks.push({ type, fn });
}
_hasTestsToRun(): boolean {
let found = false;
this.eachTest(test => {
if (!test.pending) {
found = true;
return true;
}
});
return found;
} }
} }

View File

@ -15,9 +15,9 @@
*/ */
import path from 'path'; import path from 'path';
import Mocha from 'mocha';
import { fixturesForCallback, registerWorkerFixture } from './fixtures'; import { fixturesForCallback, registerWorkerFixture } from './fixtures';
import { Configuration, NoMocha, Test, Suite } from './test'; import { Configuration, Test, Suite } from './test';
import { fixturesUI } from './fixturesUI';
export class TestCollector { export class TestCollector {
suite: Suite; suite: Suite;
@ -50,20 +50,15 @@ export class TestCollector {
} }
_addFile(file) { _addFile(file) {
const noMocha = new NoMocha(file, { const suite = new Suite('');
forbidOnly: this._options.forbidOnly, const revertBabelRequire = fixturesUI(suite, file, this._options.timeout);
timeout: this._options.timeout, require(file);
testWrapper: (test, fn) => () => {}, revertBabelRequire();
hookWrapper: (hook, fn) => () => {}, suite._renumber();
});
const workerGeneratorConfigurations = new Map(); const workerGeneratorConfigurations = new Map();
let ordinal = 0; suite.eachTest((test: Test) => {
noMocha.suite.eachTest((test: Test) => {
// All tests are identified with their ordinals.
test._ordinal = ordinal++;
// Get all the fixtures that the test needs. // Get all the fixtures that the test needs.
const fixtures = fixturesForCallback(test.fn); const fixtures = fixturesForCallback(test.fn);
@ -102,7 +97,7 @@ export class TestCollector {
// Clone the suite as many times as there are worker hashes. // Clone the suite as many times as there are worker hashes.
// Only include the tests that requested these generations. // Only include the tests that requested these generations.
for (const [hash, {configurationObject, configurationString, tests}] of workerGeneratorConfigurations.entries()) { for (const [hash, {configurationObject, configurationString, tests}] of workerGeneratorConfigurations.entries()) {
const clone = this._cloneSuite(noMocha.suite, configurationObject, configurationString, tests); const clone = this._cloneSuite(suite, configurationObject, configurationString, tests);
this.suite.addSuite(clone); this.suite.addSuite(clone);
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : ''); clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '');
} }
@ -111,9 +106,11 @@ export class TestCollector {
_cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) { _cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) {
const copy = suite.clone(); const copy = suite.clone();
copy.only = suite.only; copy.only = suite.only;
for (const child of suite.suites) for (const entry of suite._entries) {
copy.addSuite(this._cloneSuite(child, configurationObject, configurationString, tests)); if (entry instanceof Suite) {
for (const test of suite.tests) { copy.addSuite(this._cloneSuite(entry, configurationObject, configurationString, tests));
} else {
const test = entry;
if (!tests.has(test)) if (!tests.has(test))
continue; continue;
if (this._grep && !this._grep.test(test.fullTitle())) if (this._grep && !this._grep.test(test.fullTitle()))
@ -125,6 +122,7 @@ export class TestCollector {
testCopy._configurationString = configurationString; testCopy._configurationString = configurationString;
copy.addTest(testCopy); copy.addTest(testCopy);
} }
}
return copy; return copy;
} }

View File

@ -18,7 +18,8 @@ import path from 'path';
import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures'; import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { setCurrentTestFile } from './expect'; import { setCurrentTestFile } from './expect';
import { NoMocha, Runner, Test } from './test'; import { Test, Suite } from './test';
import { fixturesUI } from './fixturesUI';
export const fixturePool = new FixturePool(); export const fixturePool = new FixturePool();
@ -31,19 +32,15 @@ export type TestRunnerEntry = {
export class TestRunner extends EventEmitter { export class TestRunner extends EventEmitter {
private _currentOrdinal = -1; private _currentOrdinal = -1;
private _failedWithError = false; private _failedWithError: Error | undefined;
private _fatalError: Error | undefined;
private _file: any; private _file: any;
private _ordinals: Set<number>; private _ordinals: Set<number>;
private _remaining: Set<number>; private _remaining: Set<number>;
private _trialRun: any; private _trialRun: any;
private _passes = 0;
private _failures = 0;
private _pending = 0;
private _configuredFile: any; private _configuredFile: any;
private _configurationObject: any; private _configurationObject: any;
private _parsedGeneratorConfiguration: any = {}; private _parsedGeneratorConfiguration: any = {};
private _relativeTestFile: string;
private _runner: Runner;
private _outDir: string; private _outDir: string;
private _timeout: number; private _timeout: number;
private _testDir: string; private _testDir: string;
@ -65,134 +62,114 @@ export class TestRunner extends EventEmitter {
registerWorkerFixture(name, async ({}, test) => await test(value)); registerWorkerFixture(name, async ({}, test) => await test(value));
} }
this._parsedGeneratorConfiguration['parallelIndex'] = workerId; this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
this._relativeTestFile = path.relative(options.testDir, this._file); setCurrentTestFile(path.relative(options.testDir, this._file));
} }
async stop() { stop() {
this._trialRun = true; this._trialRun = true;
return new Promise(f => this._runner.once('done', f));
} }
async run() { async run() {
let callback;
const result = new Promise(f => callback = f);
setParameters(this._parsedGeneratorConfiguration); setParameters(this._parsedGeneratorConfiguration);
const noMocha = new NoMocha(this._file, { const suite = new Suite('');
timeout: 0, const revertBabelRequire = fixturesUI(suite, this._file, this._timeout);
testWrapper: (test, fn) => this._testWrapper(test, fn), require(this._file);
hookWrapper: (hook, fn) => this._hookWrapper(hook, fn), revertBabelRequire();
}); suite._renumber();
rerunRegistrations(this._file, 'test'); rerunRegistrations(this._file, 'test');
this._runner = noMocha.run(callback); await this._runSuite(suite);
this._reportDone();
}
this._runner.on('test', test => { private async _runSuite(suite: Suite) {
setCurrentTestFile(this._relativeTestFile); try {
await this._runHooks(suite, 'beforeAll', 'before');
} catch (e) {
this._fatalError = e;
this._reportDone();
}
for (const entry of suite._entries) {
if (entry instanceof Suite) {
await this._runSuite(entry);
} else {
await this._runTest(entry);
}
}
try {
await this._runHooks(suite, 'afterAll', 'after');
} catch (e) {
this._fatalError = e;
this._reportDone();
}
}
private async _runTest(test: Test) {
if (this._failedWithError) if (this._failedWithError)
return; return false;
const ordinal = ++this._currentOrdinal; const ordinal = ++this._currentOrdinal;
if (this._ordinals.size && !this._ordinals.has(ordinal)) if (this._ordinals.size && !this._ordinals.has(ordinal))
return; return;
this._remaining.delete(ordinal); this._remaining.delete(ordinal);
this.emit('test', { test: this._serializeTest(test, ordinal) }); if (test.pending) {
}); this.emit('pending', { test: this._serializeTest(test) });
return;
}
this._runner.on('pending', test => { this.emit('test', { test: this._serializeTest(test) });
if (this._failedWithError) try {
return; await this._runHooks(test.suite, 'beforeEach', 'before');
const ordinal = ++this._currentOrdinal; test._startTime = Date.now();
if (this._ordinals.size && !this._ordinals.has(ordinal)) if (!this._trialRun)
return; await this._testWrapper(test)();
this._remaining.delete(ordinal); this.emit('pass', { test: this._serializeTest(test) });
++this._pending; await this._runHooks(test.suite, 'afterEach', 'after');
this.emit('pending', { test: this._serializeTest(test, ordinal) }); } catch (error) {
});
this._runner.on('pass', test => {
if (this._failedWithError)
return;
const ordinal = this._currentOrdinal;
if (this._ordinals.size && !this._ordinals.has(ordinal))
return;
++this._passes;
this.emit('pass', { test: this._serializeTest(test, ordinal) });
});
this._runner.on('fail', (test, error) => {
if (this._failedWithError)
return;
++this._failures;
this._failedWithError = error; this._failedWithError = error;
this.emit('fail', { this.emit('fail', {
test: this._serializeTest(test, this._currentOrdinal), test: this._serializeTest(test),
error: serializeError(error), error: serializeError(error),
}); });
}); }
}
this._runner.once('done', async () => { private async _runHooks(suite: Suite, type: string, dir: 'before' | 'after') {
if (!suite._hasTestsToRun())
return;
const all = [];
for (let s = suite; s; s = s.parent) {
const funcs = s._hooks.filter(e => e.type === type).map(e => e.fn);
all.push(...funcs.reverse());
}
if (dir === 'before')
all.reverse();
for (const hook of all)
await fixturePool.resolveParametersAndRun(hook, 0);
}
private _reportDone() {
this.emit('done', { this.emit('done', {
stats: this._serializeStats(),
error: this._failedWithError, error: this._failedWithError,
fatalError: this._fatalError,
remaining: [...this._remaining], remaining: [...this._remaining],
total: this._passes + this._failures + this._pending
}); });
});
await result;
} }
_shouldRunTest(hook = false) { private _testWrapper(test: Test) {
if (this._trialRun || this._failedWithError)
return false;
if (hook) {
// Hook starts before we bump the test ordinal.
if (!this._ordinals.has(this._currentOrdinal + 1))
return false;
} else {
if (!this._ordinals.has(this._currentOrdinal))
return false;
}
return true;
}
_testWrapper(test: Test, fn: Function) {
const timeout = test.slow ? this._timeout * 3 : this._timeout; const timeout = test.slow ? this._timeout * 3 : this._timeout;
const wrapped = fixturePool.wrapTestCallback(fn, timeout, test, { return fixturePool.wrapTestCallback(test.fn, timeout, test, {
outputDir: this._outDir, outputDir: this._outDir,
testDir: this._testDir, testDir: this._testDir,
}); });
return wrapped ? (done, ...args) => {
if (!this._shouldRunTest()) {
done();
return;
}
wrapped(...args).then(done).catch(done);
} : undefined;
} }
_hookWrapper(hook, fn) { private _serializeTest(test) {
if (!this._shouldRunTest(true))
return;
return hook(async () => {
return await fixturePool.resolveParametersAndRun(fn, 0);
});
}
_serializeTest(test, ordinal) {
return { return {
id: `${ordinal}@${this._configuredFile}`, id: `${test._ordinal}@${this._configuredFile}`,
duration: test.duration, duration: Date.now() - test._startTime,
}; };
} }
_serializeStats() {
return {
passes: this._passes,
failures: this._failures,
pending: this._pending,
duration: this._runner.duration(),
}
}
} }
function trimCycles(obj) { function trimCycles(obj) {

View File

@ -15,14 +15,14 @@
*/ */
import { options } from './playwright.fixtures'; import { options } from './playwright.fixtures';
it.skip(options.FIREFOX)('should work', async function({page, server}) { it.skip(options.FIREFOX)('should work', async function({page}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`); await page.setContent(`<div id=d1 tabIndex=0></div>`);
expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY'); expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
await page.focus('#d1'); await page.focus('#d1');
expect(await page.evaluate(() => document.activeElement.id)).toBe('d1'); expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
}); });
it('should emit focus event', async function({page, server}) { it('should emit focus event', async function({page}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`); await page.setContent(`<div id=d1 tabIndex=0></div>`);
let focused = false; let focused = false;
await page.exposeFunction('focusEvent', () => focused = true); await page.exposeFunction('focusEvent', () => focused = true);
@ -31,7 +31,7 @@ it('should emit focus event', async function({page, server}) {
expect(focused).toBe(true); expect(focused).toBe(true);
}); });
it('should emit blur event', async function({page, server}) { it('should emit blur event', async function({page}) {
await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`); await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
await page.focus('#d1'); await page.focus('#d1');
let focused = false; let focused = false;