Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/dev-server-hmr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"build": "tsc",
"start:lit-html": "wds --config demo/lit-html/server.config.mjs",
"start:vanilla": "wds --config demo/vanilla/server.config.mjs",
"test:node": "mocha \"test/**/*.test.ts\" --require ts-node/register --reporter dot",
"test:watch": "mocha \"test/**/*.test.ts\" --require ts-node/register --watch --watch-files src,test"
"test:node": "node --experimental-strip-types --test --test-force-exit test/**/*.test.ts",
"test:watch": "node --experimental-strip-types --test --test-force-exit --watch test/**/*.test.ts"
},
"files": [
"*.d.ts",
Expand Down
122 changes: 65 additions & 57 deletions packages/dev-server-hmr/test/HmrPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { expect } from 'chai';
import { stubMethod, restore as restoreStubs } from 'hanbi';
import { describe, it, afterEach, mock } from 'node:test';
import assert from 'node:assert/strict';
import { createTestServer, fetchText, expectIncludes } from '@web/dev-server-core/test-helpers';
import { posix as pathUtil } from 'path';

import { hmrPlugin } from '../src/index.js';
import { NAME_HMR_CLIENT_IMPORT } from '../src/HmrPlugin.js';
import { mockFile, mockFiles } from './utils.js';
import { hmrPlugin } from '../dist/index.js';
import { NAME_HMR_CLIENT_IMPORT } from '../dist/HmrPlugin.js';
import { mockFile, mockFiles } from './utils.ts';

describe('HmrPlugin', () => {
afterEach(async () => {
restoreStubs();
mock.restoreAll();
});

it('should emit update for tracked files', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -26,12 +26,13 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/foo.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/foo.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/foo.js',
Expand All @@ -44,7 +45,7 @@ describe('HmrPlugin', () => {

it('should bubble updates for changed dependencies', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -55,13 +56,14 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
await fetch(`${host}/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/bar.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/foo.js',
Expand All @@ -74,22 +76,23 @@ describe('HmrPlugin', () => {

it('should not reload if dependent handles change', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile('/foo.js', `import '/bar.js'; import.meta.hot.accept();`),
mockFile('/bar.js', `export const s = 808;`),
hmrPlugin(),
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
await fetch(`${host}/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/bar.js'));

expect(stub.callCount).to.equal(1);
expect(stub.firstCall!.args[0]).to.equal(
assert.equal(stub.mock.callCount(), 1);
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/foo.js',
Expand All @@ -102,22 +105,23 @@ describe('HmrPlugin', () => {

it('should reload if dependents do not handle change', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile('/foo.js', `import '/bar.js';`),
mockFile('/bar.js', `export const s = 808;`),
hmrPlugin(),
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
await fetch(`${host}/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/bar.js'));

expect(stub.callCount).to.equal(1);
expect(stub.firstCall!.args[0]).to.equal(
assert.equal(stub.mock.callCount(), 1);
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:reload',
}),
Expand All @@ -129,7 +133,7 @@ describe('HmrPlugin', () => {

it('handles dependencies referenced relatively', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/root/foo.js',
Expand All @@ -140,13 +144,14 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/root/foo.js`);
await fetch(`${host}/root/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/root/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/root/bar.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/root/foo.js',
Expand All @@ -159,7 +164,7 @@ describe('HmrPlugin', () => {

it('should bubble updates for changed dynamic import dependencies', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -170,13 +175,14 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
await fetch(`${host}/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/bar.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/foo.js',
Expand All @@ -189,7 +195,7 @@ describe('HmrPlugin', () => {

it('imports changed dependencies with a unique URL', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFiles({
'/a.js': "import '/b.js'; import '/c.js'; import.meta.hot.accept();",
Expand All @@ -204,11 +210,11 @@ describe('HmrPlugin', () => {
await fetchText(`${host}/a.js`);
await fetchText(`${host}/b.js`);
await fetchText(`${host}/c.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/b.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/b.js'));

const updatedA = await fetchText(`${host}/a.js?m=1234567890123`);
await fetchText(`${host}/b.js?m=1234567890123`);
expect(/import '\/b\.js\?m=\d{13}';/.test(updatedA)).to.equal(true);
assert.match(updatedA, /import '\/b\.js\?m=\d{13}';/);
expectIncludes(updatedA, "import '/c.js';");
} finally {
await server.stop();
Expand All @@ -217,7 +223,7 @@ describe('HmrPlugin', () => {

it('imports deeply changed dependencies with a unique URL', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFiles({
'/a.js': "import '/b.js'; import.meta.hot.accept();",
Expand All @@ -232,21 +238,21 @@ describe('HmrPlugin', () => {
await fetchText(`${host}/a.js`);
await fetchText(`${host}/b.js`);
await fetchText(`${host}/c.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/c.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/c.js'));

const updatedA = await fetchText(`${host}/a.js?m=1234567890123`);
const updatedB = await fetchText(`${host}/b.js?m=1234567890123`);
await fetchText(`${host}/c.js?m=1234567890123`);
expect(/import '\/b\.js\?m=\d{13}';/.test(updatedA)).to.equal(true);
expect(/import '\/c\.js\?m=\d{13}';/.test(updatedB)).to.equal(true);
assert.match(updatedA, /import '\/b\.js\?m=\d{13}';/);
assert.match(updatedB, /import '\/c\.js\?m=\d{13}';/);
} finally {
await server.stop();
}
});

it('multiple dependents will import deep dependency changes with a unique URL', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFiles({
'/a1.js': "import '/b.js'; import.meta.hot.accept(); // a1",
Expand All @@ -264,22 +270,22 @@ describe('HmrPlugin', () => {
await fetchText(`${host}/a2.js`);
await fetchText(`${host}/b.js`);
await fetchText(`${host}/c.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/c.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/c.js'));

const updatedA1 = await fetchText(`${host}/a1.js?m=1234567890123`);
const updatedA2 = await fetchText(`${host}/a2.js?m=1234567890123`);
await fetchText(`${host}/b.js?m=1234567890123`);
await fetchText(`${host}/c.js?m=1234567890123`);
expect(/import '\/b\.js\?m=\d{13}';/.test(updatedA1)).to.equal(true);
expect(/import '\/b\.js\?m=\d{13}';/.test(updatedA2)).to.equal(true);
assert.match(updatedA1, /import '\/b\.js\?m=\d{13}';/);
assert.match(updatedA2, /import '\/b\.js\?m=\d{13}';/);
} finally {
await server.stop();
}
});

it('does not get confused by dynamic imports with non string literals', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -290,13 +296,14 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
await fetch(`${host}/bar.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/bar.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/bar.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:update',
url: '/foo.js',
Expand All @@ -309,7 +316,7 @@ describe('HmrPlugin', () => {

it('should emit reload for tracked files', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -321,12 +328,13 @@ describe('HmrPlugin', () => {
],
});
const { fileWatcher, webSockets } = server;
const stub = stubMethod(webSockets!, 'send');
const stub = mock.method(webSockets!, 'send');
try {
await fetch(`${host}/foo.js`);
fileWatcher.emit('change', pathUtil.join(__dirname, '/foo.js'));
fileWatcher.emit('change', pathUtil.join(import.meta.dirname, '/foo.js'));

expect(stub.firstCall!.args[0]).to.equal(
assert.equal(
stub.mock.calls[0].arguments[0],
JSON.stringify({
type: 'hmr:reload',
}),
Expand All @@ -338,22 +346,22 @@ describe('HmrPlugin', () => {

it('serves a hmr client', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [hmrPlugin()],
});

try {
const response = await fetch(`${host}${NAME_HMR_CLIENT_IMPORT}`);
const body = await response.text();
expect(body.includes('class HotModule')).to.equal(true);
assert.ok(body.includes('class HotModule'));
} finally {
await server.stop();
}
});

it('transforms hmr-capable js files', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [
mockFile(
'/foo.js',
Expand All @@ -369,23 +377,23 @@ describe('HmrPlugin', () => {
const response = await fetch(`${host}/foo.js`);
const body = await response.text();

expect(body.includes('__WDS_HMR__')).to.equal(true);
assert.ok(body.includes('__WDS_HMR__'));
} finally {
await server.stop();
}
});

it('does not transform non-hmr js files', async () => {
const { server, host } = await createTestServer({
rootDir: __dirname,
rootDir: import.meta.dirname,
plugins: [mockFile('/foo.js', `export const foo = 5;`), hmrPlugin()],
});

try {
const response = await fetch(`${host}/foo.js`);
const body = await response.text();

expect(body.includes('__WDS_HMR__')).to.equal(false);
assert.ok(!body.includes('__WDS_HMR__'));
} finally {
await server.stop();
}
Expand Down
Loading