!133 test(novdom): switch to vitest
* test(novdom): switch to vitest * test(novdom): setup vitest
This commit is contained in:
parent
332bd7ae47
commit
6ad2082444
|
@ -4,9 +4,12 @@
|
|||
"description": "no vdom runtime",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest --config=jest.config.js"
|
||||
"test": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"inula-reactive": "workspace:^0.0.1"
|
||||
"inula-reactive": "workspace:^0.0.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"@vitest/ui": "^0.34.5",
|
||||
"vitest": "^0.34.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
|
@ -13,21 +13,6 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
coverageDirectory: 'coverage',
|
||||
resetModules: true,
|
||||
|
||||
rootDir: process.cwd(),
|
||||
|
||||
setupFilesAfterEnv: [require.resolve('./tests/jest/jestSetting.js')],
|
||||
|
||||
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||
|
||||
testMatch: [
|
||||
'<rootDir>/tests/**/*.test.js',
|
||||
'<rootDir>/tests/**/*.test.ts',
|
||||
'<rootDir>/tests/**/*.test.tsx',
|
||||
],
|
||||
|
||||
timers: 'fake',
|
||||
};
|
||||
// TODO: JSX type
|
||||
export type FunctionComponent<Props = {}> = (props: Props) => unknown;
|
||||
export type AppDisposer = () => void;
|
|
@ -0,0 +1,64 @@
|
|||
import { template, insert, setAttribute, className } from '../src/dom';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('DOM manipulation functions', () => {
|
||||
describe('template function', () => {
|
||||
it('should create a node from HTML string', () => {
|
||||
const node = template('<div>Test</div>')();
|
||||
expect(node.outerHTML).toBe('<div>Test</div>');
|
||||
});
|
||||
|
||||
it('should return a clone of the node on subsequent calls', () => {
|
||||
const createNode = template('<div>Test</div>');
|
||||
const node1 = createNode();
|
||||
const node2 = createNode();
|
||||
expect(node1.isEqualNode(node2)).toBe(true);
|
||||
expect(node1).not.toBe(node2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert function', () => {
|
||||
it('should insert a text node into a parent node', () => {
|
||||
const parent = document.createElement('div');
|
||||
insert(parent, 'Test');
|
||||
expect(parent.textContent).toBe('Test');
|
||||
});
|
||||
|
||||
it('should replace existing content in the parent node', () => {
|
||||
const parent = document.createElement('div');
|
||||
parent.textContent = 'Old content';
|
||||
insert(parent, 'New content');
|
||||
expect(parent.textContent).toBe('New content');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAttribute function', () => {
|
||||
it('should set an attribute on a node', () => {
|
||||
const node = document.createElement('div');
|
||||
setAttribute(node, 'test', 'value');
|
||||
expect(node.getAttribute('test')).toBe('value');
|
||||
});
|
||||
|
||||
it('should remove an attribute from a node if value is null', () => {
|
||||
const node = document.createElement('div');
|
||||
node.setAttribute('test', 'value');
|
||||
setAttribute(node, 'test', null);
|
||||
expect(node.hasAttribute('test')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('className function', () => {
|
||||
it('should set the class of a node', () => {
|
||||
const node = document.createElement('div');
|
||||
className(node, 'test');
|
||||
expect(node.className).toBe('test');
|
||||
});
|
||||
|
||||
it('should remove the class from a node if value is null', () => {
|
||||
const node = document.createElement('div');
|
||||
node.className = 'test';
|
||||
className(node, null);
|
||||
expect(node.className).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,65 +17,49 @@ import { computed, reactive, watch } from 'inula-reactive';
|
|||
import { template as _$template, insert as _$insert, setAttribute as _$setAttribute } from '../src/dom';
|
||||
import { createComponent as _$createComponent, render } from '../src/core';
|
||||
import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../src/event';
|
||||
|
||||
import { describe, expect } from 'vitest';
|
||||
import { domTest as it } from './utils';
|
||||
import { Show } from '../src/components/Show';
|
||||
import { For } from '../src/components/For';
|
||||
|
||||
describe('test no-vdom', () => {
|
||||
it('简单的使用signal', () => {
|
||||
describe('insertion', () => {
|
||||
it('should support placeholder', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const count = reactive(0);
|
||||
* const CountingComponent = () => {
|
||||
* const [count, setCount] = useSignal(0);
|
||||
*
|
||||
* return <div id="count">Count value is {count()}.</div>;
|
||||
* };
|
||||
*
|
||||
* render(() => <CountingComponent />, container);
|
||||
*/
|
||||
|
||||
let g_count;
|
||||
const count = reactive(0);
|
||||
|
||||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`);
|
||||
const CountingComponent = () => {
|
||||
const count = reactive(0);
|
||||
g_count = count;
|
||||
let View
|
||||
|
||||
watch: if (count > 0) {
|
||||
View.$viewValue = createView(() => {})
|
||||
}
|
||||
|
||||
View = createView((() => {
|
||||
const _el$ = tmp.cloneNode(true),
|
||||
return (() => {
|
||||
const _el$ = _tmpl$(),
|
||||
_el$2 = _el$.firstChild,
|
||||
_el$4 = _el$2.nextSibling,
|
||||
_el$3 = _el$4.nextSibling;
|
||||
_$insert(_el$, count, _el$4);
|
||||
return _el$;
|
||||
})());
|
||||
|
||||
return View
|
||||
})();
|
||||
};
|
||||
function createView(el) {
|
||||
Object.defineProperty(el, '$viewValue', {
|
||||
set: value => {
|
||||
el.replaceWith(value);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render(() => _$createComponent(CountingComponent, {}), container);
|
||||
|
||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0<!---->.');
|
||||
|
||||
g_count.set(c => c + 1);
|
||||
count.set(c => c + 1);
|
||||
|
||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
});
|
||||
|
||||
it('return数组,click事件', () => {
|
||||
describe('test no-vdom', () => {
|
||||
it('return数组,click事件', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountingComponent = () => {
|
||||
|
@ -124,7 +108,7 @@ describe('test no-vdom', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
|
||||
it('return 自定义组件', () => {
|
||||
it('return 自定义组件', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
|
@ -187,7 +171,7 @@ describe('test no-vdom', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
|
||||
it('使用Show组件', () => {
|
||||
it('使用Show组件', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
|
@ -266,7 +250,7 @@ describe('test no-vdom', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
|
||||
it('使用For组件', () => {
|
||||
it('使用For组件', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const Todo = (props) => {
|
||||
|
@ -315,7 +299,9 @@ describe('test no-vdom', () => {
|
|||
|
||||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ _$template(`<div>Count value is <!>.`),
|
||||
_tmpl$2 = /*#__PURE__*/_$template(`<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push`);
|
||||
_tmpl$2 = /*#__PURE__*/ _$template(
|
||||
`<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push`
|
||||
);
|
||||
const Todo = props => {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$(),
|
||||
|
@ -363,7 +349,7 @@ describe('test no-vdom', () => {
|
|||
state.todoList.push({
|
||||
id: 27,
|
||||
title: 'Pig',
|
||||
},);
|
||||
});
|
||||
};
|
||||
return (() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
|
@ -413,7 +399,7 @@ describe('test no-vdom', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('使用effect, setAttribute, addEventListener', () => {
|
||||
it('使用effect, setAttribute, addEventListener', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean',
|
||||
|
@ -492,23 +478,53 @@ describe('test no-vdom', () => {
|
|||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ _$template(`<tr><td class="col-md-1">`),
|
||||
_tmpl$2 = /*#__PURE__*/ _$template(`<div class="col-sm-6"><button type="button">`),
|
||||
_tmpl$3 = /*#__PURE__*/_$template(`<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">`);
|
||||
const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean', 'elegant', 'easy', 'angry', 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important', 'inexpensive', 'cheap', 'expensive', 'fancy'];
|
||||
_tmpl$3 = /*#__PURE__*/ _$template(
|
||||
`<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">`
|
||||
);
|
||||
const A = [
|
||||
'pretty',
|
||||
'large',
|
||||
'big',
|
||||
'small',
|
||||
'tall',
|
||||
'short',
|
||||
'long',
|
||||
'handsome',
|
||||
'plain',
|
||||
'quaint',
|
||||
'clean',
|
||||
'elegant',
|
||||
'easy',
|
||||
'angry',
|
||||
'crazy',
|
||||
'helpful',
|
||||
'mushy',
|
||||
'odd',
|
||||
'unsightly',
|
||||
'adorable',
|
||||
'important',
|
||||
'inexpensive',
|
||||
'cheap',
|
||||
'expensive',
|
||||
'fancy',
|
||||
];
|
||||
const random = max => Math.round(Math.random() * 1000) % max;
|
||||
let nextId = 1;
|
||||
|
||||
function buildData(count) {
|
||||
let data = new Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
data[i] = {
|
||||
id: nextId++,
|
||||
label: `${A[random(A.length)]}`
|
||||
label: `${A[random(A.length)]}`,
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const Row = props => {
|
||||
const selected = computed(() => {
|
||||
return props.item.selected.get() ? "danger" : "";
|
||||
return props.item.selected.get() ? 'danger' : '';
|
||||
});
|
||||
|
||||
return (() => {
|
||||
|
@ -523,33 +539,40 @@ describe('test no-vdom', () => {
|
|||
get each() {
|
||||
return props.list;
|
||||
},
|
||||
children: item => _$createComponent(Row, {
|
||||
item: item
|
||||
})
|
||||
children: item =>
|
||||
_$createComponent(Row, {
|
||||
item: item,
|
||||
}),
|
||||
});
|
||||
};
|
||||
const Button = props => (() => {
|
||||
const Button = props =>
|
||||
(() => {
|
||||
const _el$3 = _tmpl$2(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
_$addEventListener(_el$4, "click", props.cb, true);
|
||||
_$addEventListener(_el$4, 'click', props.cb, true);
|
||||
_$insert(_el$4, () => props.title);
|
||||
watch(() => _$setAttribute(_el$4, "id", props.id));
|
||||
watch(() => _$setAttribute(_el$4, 'id', props.id));
|
||||
return _el$3;
|
||||
})();
|
||||
const Main = () => {
|
||||
const state = reactive({
|
||||
list: [{
|
||||
list: [
|
||||
{
|
||||
id: 1,
|
||||
label: '111'
|
||||
}, {
|
||||
label: '111',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '222'
|
||||
}],
|
||||
num: 2
|
||||
label: '222',
|
||||
},
|
||||
],
|
||||
num: 2,
|
||||
});
|
||||
|
||||
function run() {
|
||||
state.list.set(buildData(5));
|
||||
}
|
||||
|
||||
return (() => {
|
||||
const _el$5 = _tmpl$3(),
|
||||
_el$6 = _el$5.firstChild,
|
||||
|
@ -559,21 +582,27 @@ describe('test no-vdom', () => {
|
|||
_el$10 = _el$9.firstChild,
|
||||
_el$11 = _el$6.nextSibling,
|
||||
_el$12 = _el$11.firstChild;
|
||||
_$insert(_el$10, _$createComponent(Button, {
|
||||
id: "run",
|
||||
title: "Create 1,000 rows",
|
||||
cb: run
|
||||
}));
|
||||
_$insert(_el$12, _$createComponent(RowList, {
|
||||
_$insert(
|
||||
_el$10,
|
||||
_$createComponent(Button, {
|
||||
id: 'run',
|
||||
title: 'Create 1,000 rows',
|
||||
cb: run,
|
||||
})
|
||||
);
|
||||
_$insert(
|
||||
_el$12,
|
||||
_$createComponent(RowList, {
|
||||
get list() {
|
||||
return state.list;
|
||||
}
|
||||
}));
|
||||
},
|
||||
})
|
||||
);
|
||||
return _el$5;
|
||||
})();
|
||||
};
|
||||
render(() => _$createComponent(Main, {}), container);
|
||||
_$delegateEvents(["click"]);
|
||||
_$delegateEvents(['click']);
|
||||
|
||||
expect(container.querySelector('#tbody').innerHTML).toEqual(
|
||||
'<tr><td class="col-md-1">111</td></tr><tr><td class="col-md-1">222</td></tr>'
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
import { test } from 'vitest';
|
||||
|
||||
interface DomTestContext {
|
||||
container: HTMLDivElement;
|
||||
}
|
||||
// Define a new test type that extends the default test type and adds the container fixture.
|
||||
export const domTest = test.extend<DomTestContext>({
|
||||
container: async ({ task }, use) => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
await use(container);
|
||||
container.remove();
|
||||
},
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
|
@ -13,14 +13,11 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
global.container = null;
|
||||
global.beforeEach(() => {
|
||||
// 创建一个 DOM 元素作为渲染目标
|
||||
global.container = document.createElement('div');
|
||||
document.body.appendChild(global.container);
|
||||
});
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
global.afterEach(() => {
|
||||
global.container.remove();
|
||||
global.container = null;
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom', // or 'jsdom', 'node'
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue