Skip to content

Commit

Permalink
feat: add unit testing support (phase 1) (QwikDev#1874)
Browse files Browse the repository at this point in the history
Co-authored-by: Leifer <leifer.jesus.mendez.zambrano@external.gloval.es>
  • Loading branch information
leifermendez and Leifer authored Nov 1, 2022
1 parent ef638ba commit 7c9c65c
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 94 deletions.
31 changes: 18 additions & 13 deletions packages/qwik/src/core/component/component.unit.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { ElementFixture, trigger } from '../../testing/element-fixture';
import { createDOM } from '../../testing/library';
import { expectDOM } from '../../testing/expect-dom.unit';
import { inlinedQrl } from '../qrl/qrl';
import { useStylesQrl } from '../use/use-styles';
import { PropsOf, component$ } from './component.public';
import { suite } from 'uvu';
import { useStore } from '../use/use-store.public';
import { useLexicalScope } from '../use/use-lexical-scope.public';
import { render } from '../render/dom/render.public';

/**
* Appling new unit test library/layer
* `@builder.io/qwik/testing` ==> ../../testing/library
*/
const qComponent = suite('q-component');
qComponent('should declare and render basic component', async () => {
const fixture = new ElementFixture();
await render(fixture.host, <HelloWorld></HelloWorld>);
const { screen, render } = await createDOM();
await render(<HelloWorld />);
await expectDOM(
fixture.host,
screen,
`
<host q:version="dev" q:container="resumed" q:render="dom-dev">
<style q:style="pfkgyr-0">
Expand All @@ -28,10 +31,11 @@ qComponent('should declare and render basic component', async () => {
});

qComponent('should render Counter and accept events', async () => {
const fixture = new ElementFixture();
await render(fixture.host, <MyCounter step={5} value={15} />);
const { screen, render, userEvent } = await createDOM();

await render(<MyCounter step={5} value={15} />);
await expectDOM(
fixture.host,
screen,
`
<host q:version="dev" q:container="resumed" q:render="dom-dev">
<!--qv q:key=sX:-->
Expand All @@ -43,9 +47,9 @@ qComponent('should render Counter and accept events', async () => {
<!--/qv-->
</host>`
);
await trigger(fixture.host, 'button.decrement', 'click');
await userEvent('button.decrement', 'click');
await expectDOM(
fixture.host,
screen,
`
<host q:version="dev" q:container="resumed" q:render="dom-dev">
<!--qv q:key=sX:-->
Expand All @@ -68,7 +72,8 @@ qComponent('should render Counter and accept events', async () => {
});

qComponent('should render a collection of todo items', async () => {
const host = new ElementFixture().host;
const { screen, render } = await createDOM();

const items = {
items: [
{
Expand All @@ -81,10 +86,10 @@ qComponent('should render a collection of todo items', async () => {
},
],
};
await render(host, <Items items={items} />);
await render(<Items items={items} />);
await delay(0);
await expectDOM(
host,
screen,
`
<host q:version="dev" q:container="resumed" q:render="dom-dev">
<!--qv q:key=sX:-->
Expand Down
41 changes: 41 additions & 0 deletions packages/qwik/src/testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
### @builder.io/qwik/testing

```ts
//vite.config.ts
import { defineConfig } from 'vite';
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig(() => {
return {
plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
define: {
'globalThis.qTest': true,
'globalThis.qDev': true,
},
};
});
```

```jsx
// card.test.tsx

import { createDOM } from '@builder.io/qwik/testing';
import { test, expect } from 'vitest';
import Card from './card.tsx';

test(`[Card Component]: 🙌 Only render`, async () => {
const { screen, render } = await await createDOM();
await render(<Card />);
expect(screen.outerHTML).toContain('Counter_0');
});

test(`[Card Component]: 🙌 Click counter +1 `, async () => {
const { screen, render, userEvent } = await await createDOM();
await render(<Card />);
expect(screen.outerHTML).toContain('Counter_0');
await userEvent('button.btn-counter', 'click');
expect(screen.outerHTML).toContain('Counter_1');
});
```
68 changes: 6 additions & 62 deletions packages/qwik/src/testing/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,14 @@
```ts

import type { CorePlatform } from '@builder.io/qwik';
import type { JSXNode } from '@builder.io/qwik/jsx-runtime';

// @alpha
export function createDocument(opts?: MockDocumentOptions): Document;

// @alpha
export function createWindow(opts?: MockDocumentOptions): MockWindow;

// @alpha
export class ElementFixture {
constructor(options?: ElementFixtureOptions);
// (undocumented)
child: HTMLElement;
// (undocumented)
document: MockDocument;
// (undocumented)
host: HTMLElement;
// (undocumented)
parent: HTMLElement;
// (undocumented)
superParent: HTMLElement;
// (undocumented)
window: MockWindow;
}

// @alpha (undocumented)
export interface ElementFixtureOptions {
// (undocumented)
tagName?: string;
}

// @alpha (undocumented)
export function getTestPlatform(): TestPlatform;

// @alpha (undocumented)
export interface MockDocument extends Document {
}

// @alpha
export interface MockDocumentOptions {
// (undocumented)
html?: string;
// (undocumented)
url?: URL | string;
}

// @alpha (undocumented)
export interface MockWindow extends Window {
// (undocumented)
document: MockDocument;
}

// @alpha
export interface MockWindowOptions extends MockDocumentOptions {
}

// @alpha (undocumented)
export interface TestPlatform extends CorePlatform {
// (undocumented)
flush: () => Promise<void>;
}

// @alpha (undocumented)
export function toFileUrl(filePath: string): string;
export const createDOM: () => Promise<{
render: (jsxElement: JSXNode) => Promise<void>;
screen: HTMLElement;
userEvent: (queryOrElement: string | Element | null, eventNameCamel: string) => Promise<void>;
}>;

// (No @packageDocumentation comment for this package)

Expand Down
2 changes: 0 additions & 2 deletions packages/qwik/src/testing/document.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { setTestPlatform } from './platform';
import type { MockDocumentOptions, MockWindow } from './types';
import qwikDom from '@builder.io/qwik-dom';
import { normalizeUrl } from './util';
Expand All @@ -13,7 +12,6 @@ import { normalizeUrl } from './util';
export function createDocument(opts?: MockDocumentOptions) {
const doc = qwikDom.createDocument(opts?.html);
ensureGlobals(doc, opts);
setTestPlatform();
return doc;
}

Expand Down
10 changes: 8 additions & 2 deletions packages/qwik/src/testing/element-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ export interface ElementFixtureOptions {

/**
* Trigger an event in unit tests on an element.
*
* Posibly deprecated in the future
* @param element
* @param selector
* @param event
* @returns
* @alpha
*/
export async function trigger(
root: Element,
Expand All @@ -72,6 +73,12 @@ export async function trigger(
await getTestPlatform().flush();
}

/**
* Dispatch
* @param root
* @param attrName
* @param ev
*/
export const dispatch = async (root: Element | null, attrName: string, ev: any) => {
while (root) {
const elm = root;
Expand All @@ -85,7 +92,6 @@ export const dispatch = async (root: Element | null, attrName: string, ev: any)
root = elm.parentElement;
}
};

export function getEvent(elCtx: QContext, prop: string): any {
return qPropReadQRL(elCtx, normalizeOnProp(prop));
}
Expand Down
13 changes: 1 addition & 12 deletions packages/qwik/src/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1 @@
export { createDocument, createWindow } from './document';
export { ElementFixture } from './element-fixture';
export type { ElementFixtureOptions } from './element-fixture';
export { getTestPlatform } from './platform';
export type {
MockDocument,
MockDocumentOptions,
MockWindow,
MockWindowOptions,
TestPlatform,
} from './types';
export { toFileUrl } from './util';
export { createDOM } from './library';
57 changes: 57 additions & 0 deletions packages/qwik/src/testing/library.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { fromCamelToKebabCase } from './../core/util/case';
import { ElementFixture, dispatch } from './element-fixture';
import { setTestPlatform, getTestPlatform } from './platform';
import type { JSXNode } from '@builder.io/qwik/jsx-runtime';

/**
*
* @param root
* @param selector
* @param eventNameCamel
*/
async function triggerUserEvent(
root: Element,
selector: string,
eventNameCamel: string
): Promise<void> {
for (const element of Array.from(root.querySelectorAll(selector))) {
const kebabEventName = fromCamelToKebabCase(eventNameCamel);
const event = { type: kebabEventName };
const attrName = 'on:' + kebabEventName;
await dispatch(element, attrName, event);
}
await getTestPlatform().flush();
}

/**
* CreatePlatfrom and CreateDocument
* @alpha
*/
export const createDOM = async function () {
const qwik = await getQwik();
setTestPlatform(qwik.setPlatform);
const host = new ElementFixture().host;
return {
render: function (jsxElement: JSXNode) {
return qwik.render(host, jsxElement);
},
screen: host,
userEvent: async function (queryOrElement: string | Element | null, eventNameCamel: string) {
if (typeof queryOrElement === 'string')
return triggerUserEvent(host, queryOrElement, eventNameCamel);
const kebabEventName = fromCamelToKebabCase(eventNameCamel);
const event = { type: kebabEventName };
const attrName = 'on:' + kebabEventName;
await dispatch(queryOrElement, attrName, event);
await getTestPlatform().flush();
},
};
};

const getQwik = async (): Promise<typeof import('@builder.io/qwik')> => {
if ((globalThis as any).RUNNER !== false) {
return await import('../core/index');
} else {
return await import('@builder.io/qwik');
}
};
5 changes: 2 additions & 3 deletions packages/qwik/src/testing/platform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { setPlatform } from '../core/platform/platform';
import type { TestPlatform } from './types';
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -73,8 +72,8 @@ function createPlatform() {
return testPlatform;
}

export function setTestPlatform() {
setPlatform(testPlatform);
export function setTestPlatform(_setPlatform: Function) {
_setPlatform(testPlatform);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions scripts/submodule-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function submoduleTesting(config: BuildConfig) {
watch: watcher(config, submodule),
define: {
'globalThis.MODULE_EXT': `"mjs"`,
'globalThis.RUNNER': `false`,
},
target: 'es2020' /* needed for import.meta */,
});
Expand All @@ -52,6 +53,7 @@ export async function submoduleTesting(config: BuildConfig) {
watch: watcher(config),
define: {
'globalThis.MODULE_EXT': `"cjs"`,
'globalThis.RUNNER': `false`,
},
platform: 'node',
target: nodeTarget,
Expand Down

0 comments on commit 7c9c65c

Please sign in to comment.