Skip to content

Commit 3bf8215

Browse files
wip(tests): add unit tests
1 parent 67b1d0b commit 3bf8215

File tree

4 files changed

+2891
-59
lines changed

4 files changed

+2891
-59
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"scripts": {
2222
"coverage": "node test/coverageTest.mjs",
2323
"test": "cd test && yarn run start",
24+
"tests": "jest \"tests/.*\\.test.js\"",
2425
"dev": "webpack --mode=development --config ./build/webpack.config.js --progress --watch",
2526
"build": "cross-env-shell webpack --mode=production --config ./build/webpack.config.js --progress --env targetsBrowsers=\\\"$npm_package_browserslist\\\"",
2627
"docs": "cross-env-shell node build/evalHtmlComments.js docs/examples.md $npm_package_version && typedoc --plugin typedoc-plugin-markdown --mode file --tsconfig ./build/tsconfig.json --inputFiles ./src/index.ts --out ./docs/api --readme none --stripInternal --namedAnchors true",
@@ -29,6 +30,7 @@
2930
},
3031
"standard-version": {
3132
"scripts": {
33+
"prerelease": "yarn run tests",
3234
"postbump": "yarn run pushDocs",
3335
"postcommit": "yarn run build"
3436
}
@@ -53,8 +55,10 @@
5355
"cross-env": "^7.0.3",
5456
"crypto-browserify": "^3.12.0",
5557
"duplicate-package-checker-webpack-plugin": "^3.0.0",
58+
"jest": "^26.6.3",
5659
"path-browserify": "^1.0.1",
5760
"process": "^0.11.10",
61+
"puppeteer": "^5.5.0",
5862
"safe-buffer": "^5.2.1",
5963
"source-map-explorer": "^2.5.1",
6064
"standard-version": "^9.0.0",

tests/basic.test.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
const { defaultFiles, getPage } = require('./testsTools.js');
2+
3+
test('text-only tamplate', async () => {
4+
5+
const page = await getPage({
6+
files: {
7+
...defaultFiles,
8+
'/component.vue': `
9+
<template>
10+
Hello World ! <b id="done"/>
11+
</template>
12+
`
13+
}
14+
});
15+
16+
await page.waitForSelector('#done');
17+
await expect(page.$eval('#app', el => el.textContent.trim())).resolves.toBe('Hello World !');
18+
await page.close();
19+
});
20+
21+
22+
test('properly detect and reports errors in template', async () => {
23+
24+
const page = await getPage({
25+
files: {
26+
...defaultFiles,
27+
'/component.vue': `
28+
<template>
29+
Hello World ! {{ msg } <b id="done"/>
30+
</template>
31+
`
32+
}
33+
});
34+
35+
await page.waitForSelector('#done');
36+
await expect(page.console.some(e => e.type === 'error' && e.content[0] === 'SFC template')).toBe(true);
37+
await page.close();
38+
});
39+
40+
41+
test('properly detect and reports errors in style', async () => {
42+
43+
const page = await getPage({
44+
files: {
45+
...defaultFiles,
46+
'/component.vue': `
47+
<template>
48+
Hello World ! <b id="done"/>
49+
</template>
50+
<style>
51+
body
52+
color: red;
53+
}
54+
</style>
55+
`
56+
}
57+
});
58+
59+
await page.waitForSelector('#done');
60+
await expect(page.console.some(e => e.type === 'error' && e.content[0] === 'SFC style')).toBe(true);
61+
await page.close();
62+
});
63+
64+
65+
test('properly detect and reports errors in script', async () => {
66+
67+
const page = await getPage({
68+
files: {
69+
...defaultFiles,
70+
'/component.vue': `
71+
<script>
72+
test(
73+
</script>
74+
`
75+
}
76+
});
77+
78+
await new Promise(resolve => setTimeout(resolve, 500));
79+
await expect(page.console.some(e => e.type === 'error' && e.content[0] === 'SFC script')).toBe(true);
80+
await page.close();
81+
});
82+
83+
84+
85+
test('all blocks', async () => {
86+
87+
const page = await getPage({
88+
files: {
89+
...defaultFiles,
90+
'/component.vue': `
91+
<template>
92+
<b>Hello {{ msg }} !</b><b id="done"/>
93+
</template>
94+
<style scoped>
95+
b { color: red; }
96+
</style>
97+
<script>
98+
export default {
99+
data: () => ({ msg: 'world' })
100+
}
101+
</script>
102+
`
103+
}
104+
});
105+
106+
await new Promise(resolve => setTimeout(resolve, 500));
107+
await expect(!page.console.some(e => e.type === 'error')).toBe(true);
108+
await page.close();
109+
});
110+
111+
112+
test('invalid require', async () => {
113+
114+
const page = await getPage({
115+
files: {
116+
...defaultFiles,
117+
'/component.vue': `
118+
<script>
119+
require('empty.mjs');
120+
</script>
121+
`
122+
}
123+
});
124+
125+
await new Promise(resolve => setTimeout(resolve, 1500));
126+
await expect(page.console.some(e => e.type === 'pageerror' && String(e.content).includes('HttpError') )).toBe(true);
127+
await page.close();
128+
});
129+

tests/testsTools.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
const Fs = require('fs');
2+
const Path = require('path');
3+
const puppeteer = require('puppeteer');
4+
const mime = require('mime-types');
5+
6+
7+
const local = new URL('http://local/');
8+
9+
async function getPage({ files }) {
10+
11+
async function getFile(url, encoding) {
12+
13+
const { origin, pathname } = new URL(url);
14+
15+
if ( origin !== local.origin )
16+
return null
17+
18+
const res = {
19+
contentType: mime.lookup(Path.extname(pathname)) || '',
20+
body: files[pathname],
21+
status: files[pathname] === undefined ? 404 : 200,
22+
};
23+
24+
return res;
25+
}
26+
27+
const page = await browser.newPage();
28+
29+
page.setDefaultTimeout(2000);
30+
31+
32+
await page.setRequestInterception(true);
33+
page.on('request', async interceptedRequest => {
34+
35+
// console.log(interceptedRequest.url())
36+
37+
try {
38+
39+
const file = await getFile(interceptedRequest.url(), 'utf-8');
40+
if ( file !== null ) {
41+
42+
return void interceptedRequest.respond({
43+
...file,
44+
contentType: file.contentType + '; charset=utf-8',
45+
});
46+
}
47+
48+
interceptedRequest.continue();
49+
50+
} catch (ex) {
51+
52+
page.emit('pageerror', ex)
53+
}
54+
});
55+
56+
page.console = [];
57+
58+
//page.on('console', msg => page.console.push({ type: msg.type(), content: msg.text() }) );
59+
page.on('console', async msg => page.console.push({ type: msg.type(), content: await Promise.all( msg.args().map(e => e.jsonValue()) ) }) );
60+
page.on('pageerror', error => page.console.push({ type: 'pageerror', content: error }) );
61+
62+
page.on('error', msg => console.log('ERROR', msg));
63+
64+
//page.done = new Promise(resolve => page.exposeFunction('_done', resolve));
65+
66+
await page.goto(new URL('/index.html', local));
67+
68+
return page;
69+
}
70+
71+
let browser;
72+
73+
beforeAll(async () => {
74+
75+
browser = await puppeteer.launch({
76+
headless: !false,
77+
args: [
78+
'--incognito',
79+
'--disable-gpu',
80+
'--disable-dev-shm-usage', // for docker
81+
'--disable-accelerated-2d-canvas',
82+
'--deterministic-fetch',
83+
'--proxy-server="direct://"',
84+
'--proxy-bypass-list=*',
85+
]
86+
});
87+
});
88+
89+
afterAll(async () => {
90+
91+
await browser.close();
92+
});
93+
94+
95+
const defaultFiles = {
96+
'/vue3-sfc-loader.js': Fs.readFileSync(Path.join(__dirname, '../dist/vue3-sfc-loader.js'), { encoding: 'utf-8' }),
97+
'/index.html': `
98+
<!DOCTYPE html>
99+
<html><body>
100+
<div id="app"></div>
101+
<script src="https://unpkg.com/vue@next"></script>
102+
<script src="vue3-sfc-loader.js"></script>
103+
<script>
104+
105+
class HttpError extends Error {
106+
107+
constructor(url, res) {
108+
109+
super('HTTP error ' + res.statusCode);
110+
Error.captureStackTrace(this, this.constructor);
111+
112+
// enumerable: default false
113+
Object.defineProperties(this, {
114+
name: {
115+
value: this.constructor.name,
116+
},
117+
statusCode: {
118+
value: res.statusCode,
119+
},
120+
url: {
121+
value: url,
122+
},
123+
res: {
124+
value: res,
125+
},
126+
});
127+
}
128+
}
129+
130+
131+
const options = {
132+
133+
moduleCache: {
134+
vue: Vue
135+
},
136+
137+
getFile(path) {
138+
139+
return fetch(path).then(res => res.ok ? res.text() : Promise.reject(new HttpError(path, res)));
140+
},
141+
142+
addStyle(textContent) {
143+
144+
const style = Object.assign(document.createElement('style'), { textContent });
145+
const ref = document.head.getElementsByTagName('style')[0] || null;
146+
document.head.insertBefore(style, ref);
147+
},
148+
149+
log(type, ...args) {
150+
151+
console[type](...args);
152+
}
153+
}
154+
155+
const { loadModule } = window['vue3-sfc-loader'];
156+
const app = Vue.createApp(Vue.defineAsyncComponent( () => loadModule('./component.vue', options) ));
157+
app.mount('#app');
158+
159+
//window._done && window._done();
160+
161+
</script>
162+
</body></html>
163+
`
164+
}
165+
166+
module.exports = {
167+
defaultFiles,
168+
getPage,
169+
}

0 commit comments

Comments
 (0)