test: add testing framework
This commit is contained in:
parent
31d900ed40
commit
f109172cc4
12 changed files with 563 additions and 24 deletions
10
index.d.ts
vendored
10
index.d.ts
vendored
|
|
@ -4,13 +4,3 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./dist";
|
export * from "./dist";
|
||||||
|
|
||||||
import { HTMLElement } from "./dist";
|
|
||||||
import { IntrinsicElements as JsxIntrinsics } from "./dist/jsx";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
namespace JSX {
|
|
||||||
type Element = HTMLElement;
|
|
||||||
type IntrinsicElements = JsxIntrinsics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"clean": "ts-node scripts/clean.ts",
|
"clean": "ts-node scripts/clean.ts",
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build",
|
||||||
|
"test": "ts-node --project=test/tsconfig.json test/run-tests.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "~12",
|
"@types/node": "~12",
|
||||||
|
|
|
||||||
10
src/index.ts
10
src/index.ts
|
|
@ -6,3 +6,13 @@
|
||||||
export { HTMLElement, Component } from "./component";
|
export { HTMLElement, Component } from "./component";
|
||||||
export { UserConfig as Config } from "./config";
|
export { UserConfig as Config } from "./config";
|
||||||
export { createElement } from "./create-element";
|
export { createElement } from "./create-element";
|
||||||
|
|
||||||
|
import { HTMLElement } from "./component";
|
||||||
|
import { IntrinsicElements as JsxIntrinsics } from "./jsx";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
type Element = HTMLElement;
|
||||||
|
type IntrinsicElements = JsxIntrinsics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
212
test/lib/expect.ts
Normal file
212
test/lib/expect.ts
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { areEqual, displayValue, matches } from "./utils";
|
||||||
|
|
||||||
|
class ExpectError extends Error {
|
||||||
|
public constructor(reason: string, expected: any, actual: any) {
|
||||||
|
super(
|
||||||
|
`${reason}\n` +
|
||||||
|
`\texpected: ${displayValue(expected)}\n` +
|
||||||
|
`\tactual : ${displayValue(actual)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic expectation builder which knows nothing about the type of value it
|
||||||
|
* is operating upon.
|
||||||
|
*
|
||||||
|
* This is the base class expectation that allows only type-agnostic assertions
|
||||||
|
* upon its contained value, and all other expectation classes inherit from
|
||||||
|
* Expect.
|
||||||
|
*/
|
||||||
|
export class Expect<T> {
|
||||||
|
protected readonly value: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new expectation around a value.
|
||||||
|
*
|
||||||
|
* @param value Value to place assertions upon.
|
||||||
|
*/
|
||||||
|
public constructor(value: T) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect the value to equal an expected value.
|
||||||
|
*
|
||||||
|
* Note that strict equality checking is used for primitives and structural
|
||||||
|
* equality is used for objects.
|
||||||
|
*
|
||||||
|
* @param expected Expected value.
|
||||||
|
*
|
||||||
|
* @throws ExpectError If the actual value does not equal the expected value.
|
||||||
|
*/
|
||||||
|
public toEqual(expected: T) {
|
||||||
|
if (!areEqual(this.value, expected)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`value does not equal expected`,
|
||||||
|
expected,
|
||||||
|
this.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String-specific Expect assertions.
|
||||||
|
*/
|
||||||
|
export class StringExpect extends Expect<string> {
|
||||||
|
/**
|
||||||
|
* Expect the string value to match a RegExp pattern.
|
||||||
|
*
|
||||||
|
* @param pattern Regular expression to match against.
|
||||||
|
*
|
||||||
|
* @throws ExpectError If the actual value does not match the expected
|
||||||
|
* RegExp pattern.
|
||||||
|
*/
|
||||||
|
public toMatch(pattern: RegExp): void {
|
||||||
|
if (!this.value.match(pattern)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`value does not match expected pattern`,
|
||||||
|
pattern,
|
||||||
|
this.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect the string value to start with a particular prefix.
|
||||||
|
*
|
||||||
|
* @param prefix Prefix that the string is expected to start with.
|
||||||
|
*
|
||||||
|
* @throws ExpectError If the actual value does not start with the expected
|
||||||
|
* prefix.
|
||||||
|
*/
|
||||||
|
public toStartWith(prefix: string): void {
|
||||||
|
if (!this.value.startsWith(prefix)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`value does not start with expected prefix`,
|
||||||
|
prefix,
|
||||||
|
this.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect the string value to end with a particular suffix.
|
||||||
|
*
|
||||||
|
* @param suffix Suffix that the string is expected to end with.
|
||||||
|
*
|
||||||
|
* @throws ExpectError If the actual value does not end with the expected
|
||||||
|
* suffix.
|
||||||
|
*/
|
||||||
|
public toEndWith(suffix: string): void {
|
||||||
|
if (!this.value.endsWith(suffix)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`value does not end with expected suffix`,
|
||||||
|
suffix,
|
||||||
|
this.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function-specific Expect assertions.
|
||||||
|
*/
|
||||||
|
export class FunctionExpect<T> extends Expect<() => T> {
|
||||||
|
/**
|
||||||
|
* Expect the function to throw an Error with error message matching a
|
||||||
|
* string or pattern.
|
||||||
|
*
|
||||||
|
* @param pattern String that exactly matches the error message or RegExp
|
||||||
|
* that should match the error message.
|
||||||
|
*
|
||||||
|
* @throws ExpectError If the function does not throw an error, throws a
|
||||||
|
* non-Error value, or throws an Error whose message does not match
|
||||||
|
* the expected pattern.
|
||||||
|
*/
|
||||||
|
public toThrowErrorMatching(pattern: string | RegExp): void {
|
||||||
|
try {
|
||||||
|
this.value();
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof Error)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`function threw non-Error value`,
|
||||||
|
pattern,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!matches(error.message, pattern)) {
|
||||||
|
throw new ExpectError(
|
||||||
|
`thrown Error's message does not match pattern`,
|
||||||
|
pattern,
|
||||||
|
error.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ExpectError(
|
||||||
|
`function did not throw expected error`,
|
||||||
|
pattern,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Expect assertion builder on a string value.
|
||||||
|
*
|
||||||
|
* @param str String value to place expectations upon.
|
||||||
|
*/
|
||||||
|
export function expect(str: string): StringExpect;
|
||||||
|
/**
|
||||||
|
* Create an Expect assertion builder on a function value.
|
||||||
|
*
|
||||||
|
* Useful primarily for asserting that a function throws an expected Error,
|
||||||
|
* e.g.:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // Passes assertion.
|
||||||
|
* expect(() => throw new Error('oh noes!')).toThrowErrorMatching('oh noes!');
|
||||||
|
*
|
||||||
|
* // Fails assertion since non-error value was thrown in func.
|
||||||
|
* expect(() => throw "oh noes!").toThrow('oh noes!');
|
||||||
|
*
|
||||||
|
* // Fails assertion since func doesn't throw.
|
||||||
|
* expect(() => 1 / 2).toThrowErrorMatching('oh noes!');
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param fn Function to place expectations upon.
|
||||||
|
*/
|
||||||
|
export function expect<T>(fn: () => T): FunctionExpect<T>;
|
||||||
|
/**
|
||||||
|
* Create an Expect assertion upon some value.
|
||||||
|
*
|
||||||
|
* Expectations are declarative assertions on values that immediately throw an
|
||||||
|
* Error when the assertion is violated. This abstraction allows for readable
|
||||||
|
* test assertions like the following:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // Doesn't throw since strings are equal.
|
||||||
|
* expect("hai").toEqual("hai");
|
||||||
|
*
|
||||||
|
* // Throws an Error since 3 !== 2.
|
||||||
|
* expect(3).toEqual(2);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param value Value to place expectations upon.
|
||||||
|
*/
|
||||||
|
export function expect(value: any): Expect<any> {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return new StringExpect(value);
|
||||||
|
}
|
||||||
|
if (typeof value === "function") {
|
||||||
|
return new FunctionExpect(value);
|
||||||
|
}
|
||||||
|
return new Expect(value);
|
||||||
|
}
|
||||||
120
test/lib/harness.ts
Normal file
120
test/lib/harness.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect } from "./expect";
|
||||||
|
import { displayValue, shuffle } from "./utils";
|
||||||
|
|
||||||
|
interface Test {
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
runTest(): void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestResult = {
|
||||||
|
testName: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
result: "pass";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
result: "fail";
|
||||||
|
error: Error;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const runTest = async (test: Test): Promise<TestResult> => {
|
||||||
|
let result: TestResult;
|
||||||
|
try {
|
||||||
|
await test.runTest();
|
||||||
|
result = {
|
||||||
|
testName: test.name,
|
||||||
|
result: "pass",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
result = {
|
||||||
|
testName: test.name,
|
||||||
|
result: "fail",
|
||||||
|
error:
|
||||||
|
error instanceof Error
|
||||||
|
? error
|
||||||
|
: new Error(
|
||||||
|
`threw non-error object: ${displayValue(error)}`
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context object that is passed into a test suite definition.
|
||||||
|
*/
|
||||||
|
export interface TestSuiteContext {
|
||||||
|
/**
|
||||||
|
* Define a test in this test suite.
|
||||||
|
*
|
||||||
|
* Tests are functions that pass if they are executed and don't throw (or
|
||||||
|
* that resolve for async tests), and that fail if they throw an error (or
|
||||||
|
* reject for async tests).
|
||||||
|
*
|
||||||
|
* Note that tests are executed in a random order within a test suite in
|
||||||
|
* order to prevent accidentally creating order dependencies between tests,
|
||||||
|
* which can result in brittle tests and is a code smell that might indicate
|
||||||
|
* that the code under test is also brittle.
|
||||||
|
*/
|
||||||
|
test: (name: string, def: () => void | Promise<void>) => void;
|
||||||
|
/**
|
||||||
|
* Expectation builder function used to build human-readable assertions and
|
||||||
|
* errors.
|
||||||
|
*/
|
||||||
|
expect: typeof expect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a suite of tests to run as a single unit.
|
||||||
|
*
|
||||||
|
* A test suite executes immediately, running tests in a randomly determined
|
||||||
|
* order.
|
||||||
|
*
|
||||||
|
* Note that currently there is no support for having multiple test suites per
|
||||||
|
* test file; you CAN have multiple test suites in a file but if the first test
|
||||||
|
* suite fails any subsequent test suites won't be executed.
|
||||||
|
*
|
||||||
|
* @param suiteName Name of the test suite for reporting.
|
||||||
|
* @param def Function used to declare the tests
|
||||||
|
*/
|
||||||
|
export const testSuite = (
|
||||||
|
suiteName: string,
|
||||||
|
def: (ctx: TestSuiteContext) => void
|
||||||
|
): void => {
|
||||||
|
const tests: Test[] = [];
|
||||||
|
const test = (name: string, runTest: () => void | Promise<void>): void => {
|
||||||
|
tests.push({ name, runTest });
|
||||||
|
};
|
||||||
|
def({ test, expect });
|
||||||
|
|
||||||
|
// Randomly shuffle the tests so that we can catch accidental order
|
||||||
|
// dependencies.
|
||||||
|
shuffle(tests);
|
||||||
|
(async () => {
|
||||||
|
const results = await Promise.all(tests.map((test) => runTest(test)));
|
||||||
|
let passed = 0;
|
||||||
|
for (const testResult of results) {
|
||||||
|
if (testResult.result === "fail") {
|
||||||
|
console.error(
|
||||||
|
`[TEST FAILURE] "${suiteName}": "${testResult.testName}": ` +
|
||||||
|
`${testResult.error.message}\n`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
passed += 1;
|
||||||
|
}
|
||||||
|
console.info(
|
||||||
|
`[TEST] suite "${suiteName}": ${passed} of ${tests.length} succeeded\n\n`
|
||||||
|
);
|
||||||
|
if (passed < tests.length) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
6
test/lib/index.ts
Normal file
6
test/lib/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { testSuite } from "./harness";
|
||||||
106
test/lib/utils.ts
Normal file
106
test/lib/utils.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomly rearrange the items of an array in-place.
|
||||||
|
*
|
||||||
|
* @param arr Array to shuffle.
|
||||||
|
*/
|
||||||
|
export const shuffle = <T>(arr: T[]): void => {
|
||||||
|
let j: number;
|
||||||
|
let x: T;
|
||||||
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
|
j = Math.floor(Math.random() * (i + 1));
|
||||||
|
x = arr[i];
|
||||||
|
arr[i] = arr[j];
|
||||||
|
arr[j] = x;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const areArraysEqual = <T>(a: T[], b: T[]): boolean => {
|
||||||
|
if (a.length != b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!areEqual(a[i], b[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const areObjectsEqual = <T extends object>(a: T, b: T): boolean => {
|
||||||
|
const aKeys = Object.keys(a) as Array<keyof T>;
|
||||||
|
const bKeys = Object.keys(b) as Array<keyof T>;
|
||||||
|
if (aKeys.length !== bKeys.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const key of aKeys) {
|
||||||
|
if (!areEqual(a[key], b[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether two values are structurally equal, with support for
|
||||||
|
* primitive values, arrays, deeply nested objects, and RegExp.
|
||||||
|
*
|
||||||
|
* @param a First value to test equality with.
|
||||||
|
* @param b Second value to test equality with.
|
||||||
|
*
|
||||||
|
* @return Whether the two values are structurally equal.
|
||||||
|
*/
|
||||||
|
export const areEqual = <T>(a: T, b: T): boolean => {
|
||||||
|
if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
|
return areArraysEqual(a, b);
|
||||||
|
}
|
||||||
|
if (a instanceof RegExp && b instanceof RegExp) {
|
||||||
|
return a.source === b.source;
|
||||||
|
}
|
||||||
|
if (typeof a === "object" && typeof b === "object") {
|
||||||
|
return areObjectsEqual(a as any, b);
|
||||||
|
}
|
||||||
|
return a === b;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a string exactly matches an expected string OR matches a
|
||||||
|
* RegExp pattern.
|
||||||
|
*
|
||||||
|
* If the passed pattern is a string this uses strict equality checking, and
|
||||||
|
* if the passed pattern is a RegExp object it tests the value against it.
|
||||||
|
*
|
||||||
|
* @param value String value to test.
|
||||||
|
* @param pattern String or RegExp pattern to match value against.
|
||||||
|
*/
|
||||||
|
export const matches = (value: string, pattern: string | RegExp): boolean => {
|
||||||
|
if (typeof pattern === "string") {
|
||||||
|
return value === pattern;
|
||||||
|
}
|
||||||
|
return pattern.test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a JavaScript value for debugging and error messages.
|
||||||
|
*
|
||||||
|
* This is essentially JSON.stringify, but with special cases for undefined (
|
||||||
|
* which normally isn't rendered with JSON.stringify) and RegExp (to display
|
||||||
|
* source for the regexp).
|
||||||
|
*
|
||||||
|
* @param value Value to render.
|
||||||
|
*
|
||||||
|
* @return Rendered value to display.
|
||||||
|
*/
|
||||||
|
export const displayValue = (value: any): string => {
|
||||||
|
if (value === undefined) {
|
||||||
|
return "undefined";
|
||||||
|
}
|
||||||
|
if (value instanceof RegExp) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
return JSON.stringify(value);
|
||||||
|
};
|
||||||
20
test/run-tests.ts
Normal file
20
test/run-tests.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fork } from "child_process";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
import { shuffle } from "./lib/utils";
|
||||||
|
|
||||||
|
const TEST_SUITES_DIR = path.join(__dirname, "test-suites");
|
||||||
|
|
||||||
|
const files = fs.readdirSync(TEST_SUITES_DIR);
|
||||||
|
// Shuffle test suites to detect ordering dependencies between them.
|
||||||
|
shuffle(files);
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = path.join(TEST_SUITES_DIR, file);
|
||||||
|
fork(path.relative(process.cwd(), fullPath));
|
||||||
|
}
|
||||||
64
test/test-suites/rendering.tsx
Normal file
64
test/test-suites/rendering.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createElement } from "../../dist";
|
||||||
|
import { renderPage } from "../../dist/render";
|
||||||
|
import { testSuite } from "../lib";
|
||||||
|
|
||||||
|
testSuite("renderPage", ({ test, expect }) => {
|
||||||
|
test("throws an Error when root elem is not html tag", () => {
|
||||||
|
expect(() => renderPage(<div />)).toThrowErrorMatching(
|
||||||
|
"attempted to render page with non-HTML root element div"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("outputs a HTML5 DOCTYPE declaration", () => {
|
||||||
|
const html = renderPage(<html />);
|
||||||
|
expect(html).toStartWith("<!DOCTYPE html>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("escapes HTML in tag names", () => {
|
||||||
|
const html = renderPage(
|
||||||
|
<html>{createElement("div></div", null)}</html>
|
||||||
|
);
|
||||||
|
expect(html).toEqual(
|
||||||
|
"<!DOCTYPE html><html><div></div></div></div></html>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders html attributes", () => {
|
||||||
|
const html = renderPage(
|
||||||
|
<html>
|
||||||
|
<div className="test" id="1" />
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
expect(html).toEqual(
|
||||||
|
'<!DOCTYPE html><html><div class="test" id="1"></div></html>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders common html tags", () => {
|
||||||
|
const html = renderPage(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
expect(html).toEqual(
|
||||||
|
"<!DOCTYPE html><html><head><title></title></head><body><div></div></body></html>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders text nodes", () => {
|
||||||
|
const html = renderPage(<html>There are three lights!</html>);
|
||||||
|
expect(html).toEqual(
|
||||||
|
"<!DOCTYPE html><html>There are three lights!</html>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
7
test/tsconfig.json
Normal file
7
test/tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig-base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "createElement"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig-base.json
Normal file
14
tsconfig-base.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2019",
|
||||||
|
"lib": ["ES2019"],
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,7 @@
|
||||||
{
|
{
|
||||||
|
"extends": "./tsconfig-base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true,
|
"outDir": "./dist"
|
||||||
"declaration": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "CommonJS",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "ES2019",
|
|
||||||
"lib": ["ES2019"],
|
|
||||||
"outDir": "./dist",
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"]
|
"include": ["src/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue