Skip to content

Commit 965e8b1

Browse files
committed
test: Add and improve tests
1 parent 41720a3 commit 965e8b1

6 files changed

+212
-49
lines changed

test/setup.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ expect.extend({
2929
},
3030
});
3131

32+
export const originalConsoleCtor = global.console.Console;
33+
3234
const originalConsole = global.console;
3335
const noop = () => {};
3436
const noopAsync = () => Promise.resolve();

test/unit/newtab.test.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { afterEach, describe, expect, spyOn, test } from 'bun:test';
22
import { reset } from '../setup';
33
import { DECLARATION, compile, lookup, walk } from './css-engine';
4-
import { consoleSpy } from './utils';
4+
import { performanceSpy } from './utils';
55

66
// Completely reset DOM and global state between tests
77
afterEach(reset);
@@ -18,7 +18,6 @@ async function load() {
1818
}
1919

2020
test('renders entire newtab app', async () => {
21-
const checkConsoleSpy = consoleSpy();
2221
await load();
2322
expect(document.body.innerHTML.length).toBeGreaterThan(1000);
2423
expect(document.body.querySelector('#b')).toBeTruthy();
@@ -28,22 +27,31 @@ test('renders entire newtab app', async () => {
2827

2928
// TODO: More/better assertions
3029
// TODO: Check all section headings exist; a h2 with text 'Open Tabs' x5
30+
});
3131

32-
checkConsoleSpy();
32+
test('does not call any console methods', async () => {
33+
await load();
34+
expect(happyDOM.virtualConsolePrinter.read()).toBeArrayOfSize(0);
3335
});
3436

35-
test('gets stored user settings once', async () => {
36-
const spy = spyOn(chrome.storage.local, 'get');
37+
test('does not call any performance methods', async () => {
38+
const check = performanceSpy();
3739
await load();
38-
expect(spy).toHaveBeenCalledTimes(1);
40+
check();
3941
});
4042

41-
test('makes no fetch() calls', async () => {
43+
test('does not call fetch()', async () => {
4244
const spy = spyOn(global, 'fetch');
4345
await load();
4446
expect(spy).not.toHaveBeenCalled();
4547
});
4648

49+
test('gets stored user settings once', async () => {
50+
const spy = spyOn(chrome.storage.local, 'get');
51+
await load();
52+
expect(spy).toHaveBeenCalledTimes(1);
53+
});
54+
4755
// TODO: Test with various settings
4856
// TODO: Test themes logic
4957
const css = await Bun.file('dist/newtab.css').text();

test/unit/settings.test.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { afterEach, describe, expect, mock, spyOn, test } from 'bun:test';
22
import { reset } from '../setup';
33
import { DECLARATION, compile, lookup, walk } from './css-engine';
4-
import { consoleSpy } from './utils';
4+
import { performanceSpy } from './utils';
55

66
// Completely reset DOM and global state between tests
77
afterEach(reset);
@@ -27,27 +27,35 @@ async function load() {
2727
}
2828

2929
test('renders entire settings app', async () => {
30-
const checkConsoleSpy = consoleSpy();
3130
await load();
3231
expect(document.body.innerHTML.length).toBeGreaterThan(600);
3332

3433
// TODO: More/better assertions
34+
});
3535

36-
checkConsoleSpy();
36+
test('does not call any console methods', async () => {
37+
await load();
38+
expect(happyDOM.virtualConsolePrinter.read()).toBeArrayOfSize(0);
3739
});
3840

39-
test('gets stored user settings once on load', async () => {
40-
const spy = spyOn(chrome.storage.local, 'get');
41+
test('does not call any performance methods', async () => {
42+
const check = performanceSpy();
4143
await load();
42-
expect(spy).toHaveBeenCalledTimes(1);
44+
check();
4345
});
4446

45-
test('fetches themes.json once and makes no other fetch calls', async () => {
47+
test('fetches themes.json once and does no other fetch calls', async () => {
4648
const fetchMock = await load();
4749
expect(fetchMock).toHaveBeenCalledTimes(1);
4850
expect(fetchMock).toHaveBeenCalledWith('themes.json');
4951
});
5052

53+
test('gets stored user settings once on load', async () => {
54+
const spy = spyOn(chrome.storage.local, 'get');
55+
await load();
56+
expect(spy).toHaveBeenCalledTimes(1);
57+
});
58+
5159
const css = await Bun.file('dist/settings.css').text();
5260

5361
describe('CSS', () => {

test/unit/test-setup.test.ts

+135-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
import { describe, expect, test } from 'bun:test';
22
import { VirtualConsole } from 'happy-dom';
3+
import * as setupExports from '../setup';
4+
import { originalConsoleCtor, reset } from '../setup';
5+
6+
describe('exports', () => {
7+
const exports = ['originalConsoleCtor', 'reset'];
8+
9+
test.each(exports)('has "%s" named export', (exportName) => {
10+
expect(setupExports).toHaveProperty(exportName);
11+
});
12+
13+
test('does not have a default export', () => {
14+
expect(setupExports).not.toHaveProperty('default');
15+
});
16+
17+
test('does not export anything else', () => {
18+
expect(Object.keys(setupExports)).toHaveLength(exports.length);
19+
});
20+
});
321

422
describe('matcher: toBePlainObject', () => {
523
const plainObjects = [
@@ -67,15 +85,12 @@ describe('console2', () => {
6785
expect(console2).toBeDefined();
6886
});
6987

70-
// TODO: How to test this? Since setup.ts is preloaded, there's no way to get
71-
// the original console.
72-
// test('is the original console', () => {
73-
// expect(console2).toBe(originalConsole);
74-
// });
88+
test('is the original console', () => {
89+
expect(console2).toBeInstanceOf(originalConsoleCtor);
90+
});
7591

76-
test('is not a happy-dom virtual console', () => {
77-
expect(window.console).toBeInstanceOf(VirtualConsole);
78-
expect(console).toStrictEqual(window.console);
92+
test('is not the happy-dom virtual console', () => {
93+
expect(console2).not.toBeInstanceOf(VirtualConsole);
7994
expect(console2).not.toBe(console);
8095
expect(console2).not.toBe(window.console);
8196
});
@@ -87,13 +102,12 @@ describe('happy-dom', () => {
87102
'window',
88103
'document',
89104
'console',
105+
'fetch',
90106
'setTimeout',
91107
'clearTimeout',
92108
'DocumentFragment',
93109
'CSSStyleSheet',
94110
'Text',
95-
'fetch',
96-
'MutationObserver',
97111
];
98112

99113
test.each(globals)('"%s" global exists', (global) => {
@@ -103,6 +117,116 @@ describe('happy-dom', () => {
103117
test('console is a virtual console', () => {
104118
expect(window.console).toBeInstanceOf(VirtualConsole);
105119
expect(console).toBeInstanceOf(VirtualConsole);
106-
expect(console).toStrictEqual(window.console);
120+
expect(console).toBe(window.console); // same instance
121+
});
122+
123+
test('console is not the original console', () => {
124+
expect(console).not.toBeInstanceOf(originalConsoleCtor);
125+
expect(console).not.toBe(console2);
126+
});
127+
128+
describe('virtual console', () => {
129+
test('has no log entries by default', () => {
130+
const logs = happyDOM.virtualConsolePrinter.read();
131+
expect(logs).toBeArray();
132+
expect(logs).toHaveLength(0);
133+
});
134+
135+
// types shouldn't include @types/node Console['Console'] property
136+
const methods: (keyof Omit<Console, 'Console'>)[] = [
137+
'assert',
138+
// 'clear', // clears log entries so we can't test it
139+
'count',
140+
'countReset',
141+
'debug',
142+
'dir',
143+
'dirxml',
144+
'error',
145+
// @ts-expect-error - alias for console.error
146+
'exception',
147+
'group',
148+
'groupCollapsed',
149+
// 'groupEnd', // doesn't log anything
150+
'info',
151+
'log',
152+
// 'profile', // not implemented in happy-dom
153+
// 'profileEnd',
154+
'table',
155+
// 'time', // doesn't log anything
156+
// 'timeStamp',
157+
// 'timeLog',
158+
// 'timeEnd',
159+
'trace',
160+
'warn',
161+
];
162+
163+
test.each(methods)('has log entry after "%s" call', (method) => {
164+
// eslint-disable-next-line no-console
165+
console[method]();
166+
expect(happyDOM.virtualConsolePrinter.read()).toHaveLength(1);
167+
});
168+
169+
test('clears log entries after read', () => {
170+
expect(happyDOM.virtualConsolePrinter.read()).toHaveLength(0);
171+
// eslint-disable-next-line no-console
172+
console.log();
173+
expect(happyDOM.virtualConsolePrinter.read()).toHaveLength(1);
174+
expect(happyDOM.virtualConsolePrinter.read()).toHaveLength(0);
175+
});
176+
});
177+
});
178+
179+
describe('reset', () => {
180+
test('is a function', () => {
181+
expect(reset).toBeFunction();
182+
});
183+
184+
test('takes no arguments', () => {
185+
expect(reset).toHaveLength(0);
186+
});
187+
188+
test('resets global chrome instance', async () => {
189+
(chrome as typeof chrome & { foo?: string }).foo = 'bar';
190+
expect(chrome).toHaveProperty('foo', 'bar');
191+
await reset();
192+
expect(chrome).not.toHaveProperty('foo');
193+
});
194+
195+
test('resets global window instance', async () => {
196+
(window as Window & { foo?: string }).foo = 'bar';
197+
expect(window).toHaveProperty('foo', 'bar');
198+
await reset();
199+
expect(window).not.toHaveProperty('foo');
200+
});
201+
202+
test('resets global document instance', async () => {
203+
const h1 = document.createElement('h1');
204+
h1.textContent = 'foo';
205+
document.body.appendChild(h1);
206+
expect(document.documentElement.innerHTML).toBe('<head></head><body><h1>foo</h1></body>');
207+
await reset();
208+
expect(document.documentElement.innerHTML).toBe('<head></head><body></body>');
209+
});
210+
211+
test('resets expected globals instances', async () => {
212+
const oldChrome = chrome;
213+
const oldHappyDOM = happyDOM;
214+
const oldWindow = window;
215+
const oldDocument = document;
216+
const oldConsole = console;
217+
const oldFetch = fetch;
218+
const oldSetTimeout = setTimeout;
219+
const oldClearTimeout = clearTimeout;
220+
const oldDocumentFragment = DocumentFragment;
221+
await reset();
222+
expect(chrome).not.toBe(oldChrome);
223+
expect(happyDOM).not.toBe(oldHappyDOM);
224+
expect(window).not.toBe(oldWindow);
225+
expect(document).not.toBe(oldDocument);
226+
expect(console).not.toBe(oldConsole);
227+
expect(fetch).not.toBe(oldFetch);
228+
expect(setTimeout).not.toBe(oldSetTimeout);
229+
expect(clearTimeout).not.toBe(oldClearTimeout);
230+
expect(DocumentFragment).not.toBe(oldDocumentFragment);
107231
});
108232
});

test/unit/test-utils.test.ts

+39-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
import { afterEach, describe, expect, spyOn, test } from 'bun:test';
22
import { Test } from '../TestComponent';
3-
import { cleanup, consoleSpy, render } from './utils';
3+
import * as utilsExports from './utils';
4+
import { cleanup, performanceSpy, render } from './utils';
5+
6+
describe('exports', () => {
7+
const exports = ['cleanup', 'performanceSpy', 'render'];
8+
9+
test.each(exports)('has "%s" named export', (exportName) => {
10+
expect(utilsExports).toHaveProperty(exportName);
11+
});
12+
13+
test('does not have a default export', () => {
14+
expect(utilsExports).not.toHaveProperty('default');
15+
});
16+
17+
test('does not export anything else', () => {
18+
expect(Object.keys(utilsExports)).toHaveLength(exports.length);
19+
});
20+
});
421

522
describe('render (no call)', () => {
623
test('is a function', () => {
@@ -161,38 +178,40 @@ describe('cleanup', () => {
161178
});
162179
});
163180

164-
describe('consoleSpy', () => {
181+
describe('performanceSpy', () => {
165182
test('is a function', () => {
166-
expect(consoleSpy).toBeInstanceOf(Function);
183+
expect(performanceSpy).toBeInstanceOf(Function);
167184
});
168185

169186
test('takes no arguments', () => {
170-
expect(consoleSpy).toHaveLength(0);
187+
expect(performanceSpy).toHaveLength(0);
171188
});
172189

173190
test('returns a function', () => {
174-
const checkConsoleSpy = consoleSpy();
175-
expect(checkConsoleSpy).toBeInstanceOf(Function);
176-
checkConsoleSpy();
191+
const check = performanceSpy();
192+
expect(check).toBeInstanceOf(Function);
193+
check();
177194
});
178195

179196
test('returned function takes no arguments', () => {
180-
const checkConsoleSpy = consoleSpy();
181-
expect(checkConsoleSpy).toHaveLength(0);
182-
checkConsoleSpy();
197+
const check = performanceSpy();
198+
expect(check).toHaveLength(0);
199+
check();
183200
});
184201

185-
test('passes when no console methods are called', () => {
186-
const checkConsoleSpy = consoleSpy();
187-
checkConsoleSpy();
202+
test('passes when no performance methods are called', () => {
203+
const check = performanceSpy();
204+
check();
188205
});
189206

190-
// FIXME: How to test this?
191-
test.skip('fails when console methods are called', () => {
192-
const checkConsoleSpy = consoleSpy();
193-
console.log('a');
194-
console.warn('b');
195-
console.error('c');
196-
checkConsoleSpy();
207+
// TODO: Don't skip this once test.failing() is supported in bun. We need to
208+
// check that the expect() inside the performanceSpy() fails (meaning this
209+
// test should then be a pass).
210+
// ↳ https://jestjs.io/docs/api#testfailingname-fn-timeout
211+
test.skip('fails when performance methods are called', () => {
212+
const check = performanceSpy();
213+
performance.mark('a');
214+
performance.measure('a', 'a');
215+
check();
197216
});
198217
});

test/unit/utils.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ export function cleanup(): void {
5454
});
5555
}
5656

57-
const consoleMethods = Object.getOwnPropertyNames(console) as (keyof Console)[];
57+
const methods = Object.getOwnPropertyNames(
58+
performance,
59+
) as (keyof Performance)[];
5860

59-
export function consoleSpy(): () => void {
61+
export function performanceSpy(): () => void {
6062
const spies: Mock<() => void>[] = [];
6163

62-
for (const method of consoleMethods) {
63-
spies.push(spyOn(console, method));
64+
for (const method of methods) {
65+
spies.push(spyOn(performance, method));
6466
}
6567

6668
return /** check */ () => {

0 commit comments

Comments
 (0)