Skip to content

Commit

Permalink
test: Refactor tests to use new @maxmilton/test-utils package
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmilton committed Oct 27, 2024
1 parent 4394e5f commit 60ab6a5
Show file tree
Hide file tree
Showing 8 changed files with 5 additions and 1,983 deletions.
169 changes: 2 additions & 167 deletions test/setup.ts
Original file line number Diff line number Diff line change
@@ -1,174 +1,9 @@
import { expect } from 'bun:test';
import { GlobalWindow, type Window } from 'happy-dom';
import '@maxmilton/test-utils/extend';

/* eslint-disable no-var, vars-on-top */
declare global {
/** Real bun console. `console` is mapped to happy-dom's virtual console. */
var $console: Console;
var happyDOM: Window['happyDOM'];
}
/* eslint-enable */

/**
* Get the total number of parameters of a function including optional
* parameters with default values.
*
* @remarks Native functions will only return the number of required parameters;
* optional parameters cannot be determined.
*
* @returns The number of parameters, including optional parameters.
*/
export function parameters(func: unknown): number {
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}

const str = func.toString();
const len = str.length;
const start = str.indexOf('(');
let index = start;
let count = 1;
let nested = 0;
let char: string;

// FIXME: Handle nested string template literals.
const string = (quote: '"' | "'" | '`') => {
while (index++ < len) {
char = str[index];

if (char === quote) {
break;
}
// skip escaped characters
if (char === '\\') {
index++;
}
}
};

while (index++ < len) {
char = str[index];

if (!nested) {
if (char === ')') {
break;
}
if (char === ',') {
count++;
continue; // eslint-disable-line no-continue
}
}

switch (char) {
case '"':
case "'":
case '`':
string(char);
break;
case '(':
case '[':
case '{':
nested++;
break;
case ')':
case ']':
case '}':
nested--;
break;
default:
break;
}
}

if (index >= len || nested !== 0) {
throw new Error('Invalid function signature');
}

// handle no parameters
if (str.slice(start + 1, index).trim().length === 0) {
if (str.indexOf('[native code]', index) >= 0) {
count = func.length;
// eslint-disable-next-line no-console
console.warn('Optional parameters cannot be determined for native functions');
} else {
count = 0;
}
}
import { setupDOM } from '@maxmilton/test-utils/dom';

return count;
}

declare module 'bun:test' {
interface Matchers {
/** Asserts that a value is a plain `object`. */
toBePlainObject(): void;
/** Asserts that a value is a `class`. */
toBeClass(): void;
/** Asserts that a function has a specific number of parameters. */
toHaveParameters(required: number, optional: number): void;
}
}

expect.extend({
// XXX: Bun's `toBeObject` matcher is the equivalent of `typeof x === 'object'`.
toBePlainObject(received: unknown) {
return Object.prototype.toString.call(received) === '[object Object]'
? { pass: true }
: {
pass: false,
message: () => `expected ${String(received)} to be a plain object`,
};
},

toBeClass(received: unknown) {
return typeof received === 'function' &&
/^class\s/.test(Function.prototype.toString.call(received))
? { pass: true }
: {
pass: false,
message: () => `expected ${String(received)} to be a class`,
};
},

toHaveParameters(received: unknown, required: number, optional: number) {
if (typeof received !== 'function') {
return {
pass: false,
message: () => `expected ${String(received)} to be a function`,
};
}

const actualRequired = received.length;
const actualOptional = parameters(received) - actualRequired;

return actualRequired === required && actualOptional === optional
? { pass: true }
: {
pass: false,
message: () =>
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`expected ${received.name} to have ${required}/${optional} required/optional parameters, but it has ${actualRequired}/${actualOptional}`,
};
},
});

export const originalConsoleCtor = global.console.Console;

const originalConsole = global.console;
const noop = () => {};

function setupDOM() {
const dom = new GlobalWindow();
global.happyDOM = dom.happyDOM;
global.$console = originalConsole;
// @ts-expect-error - happy-dom only implements a subset of the DOM API
global.window = dom.window.document.defaultView;
global.document = window.document;
global.console = window.console; // https://github.com/capricorn86/happy-dom/wiki/Virtual-Console
global.MutationObserver = window.MutationObserver;
global.DocumentFragment = window.DocumentFragment;
}

function setupMocks(): void {
// @ts-expect-error - noop stub
global.performance.mark = noop;
Expand Down
2 changes: 1 addition & 1 deletion test/unit/browser-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// XXX: This file has the same tests as test/unit/runtime.test.ts, keep them in sync.

import { afterEach, describe, expect, test } from 'bun:test';
import { cleanup, render } from '@maxmilton/test-utils/dom';
import { collect, h, html } from '../../src/browser/runtime';
import { cleanup, render } from './utils';

describe('h', () => {
test('is a function', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, mock, test } from 'bun:test';
import { cleanup, render } from '@maxmilton/test-utils/dom';
import { deleteSyntheticEvent, setupSyntheticEvent } from '../../src/events';
import { cleanup, render } from './utils';

declare global {
interface HTMLElement {
Expand Down
2 changes: 0 additions & 2 deletions test/unit/macro.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// XXX: This file has the same tests as test/unit/compile.test.ts, keep them in sync.

import { describe, expect, spyOn, test } from 'bun:test';
// eslint-disable-next-line import/no-duplicates
import { compile } from '../../src/macro' with { type: 'macro' };
// eslint-disable-next-line import/no-duplicates
import { compile as compileNoMacro } from '../../src/macro';

describe('compile', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/runtime.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// XXX: This file has the same tests as test/unit/compile.test.ts, keep them in sync.

import { afterEach, describe, expect, test } from 'bun:test';
import { cleanup, render } from '@maxmilton/test-utils/dom';
import { compile } from '../../src/macro' with { type: 'macro' };
import { collect, h } from '../../src/runtime';
import type { Refs } from '../../src/types';
import { cleanup, render } from './utils';

describe('h', () => {
test('is a function', () => {
Expand Down
Loading

0 comments on commit 60ab6a5

Please sign in to comment.