@preact/signals-react 1.3.5 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/signals.d.ts +1 -1
- package/dist/signals.js +1 -1
- package/dist/signals.js.map +1 -1
- package/dist/signals.min.js +1 -1
- package/dist/signals.min.js.map +1 -1
- package/dist/signals.mjs +1 -1
- package/dist/signals.mjs.map +1 -1
- package/dist/signals.module.js +1 -1
- package/dist/signals.module.js.map +1 -1
- package/package.json +11 -1
- package/runtime/dist/auto.d.ts +1 -0
- package/runtime/dist/index.d.ts +3 -2
- package/runtime/dist/runtime.js +1 -1
- package/runtime/dist/runtime.js.map +1 -1
- package/runtime/dist/runtime.min.js +1 -1
- package/runtime/dist/runtime.min.js.map +1 -1
- package/runtime/dist/runtime.mjs +1 -1
- package/runtime/dist/runtime.mjs.map +1 -1
- package/runtime/dist/runtime.module.js +1 -1
- package/runtime/dist/runtime.module.js.map +1 -1
- package/runtime/src/auto.ts +7 -3
- package/runtime/src/index.ts +50 -8
- package/src/index.ts +6 -2
- package/runtime/test/useSignals.test.tsx +0 -422
- package/test/browser/exports.test.tsx +0 -18
- package/test/browser/mounts.test.tsx +0 -32
- package/test/browser/react-router.test.tsx +0 -63
- package/test/browser/updates.test.tsx +0 -735
- package/test/node/renderToStaticMarkup.test.tsx +0 -22
- package/test/shared/mounting.tsx +0 -184
- package/test/shared/utils.ts +0 -127
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { signal, useSignalEffect } from "@preact/signals-react";
|
|
2
|
-
import { createElement } from "react";
|
|
3
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
import { mountSignalsTests } from "../shared/mounting";
|
|
5
|
-
|
|
6
|
-
describe("renderToStaticMarkup", () => {
|
|
7
|
-
mountSignalsTests(renderToStaticMarkup);
|
|
8
|
-
|
|
9
|
-
it("should not invoke useSignalEffect", async () => {
|
|
10
|
-
const spy = sinon.spy();
|
|
11
|
-
const sig = signal("foo");
|
|
12
|
-
|
|
13
|
-
function App() {
|
|
14
|
-
useSignalEffect(() => spy(sig.value));
|
|
15
|
-
return <p>{sig.value}</p>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const html = await renderToStaticMarkup(<App />);
|
|
19
|
-
expect(html).to.equal("<p>foo</p>");
|
|
20
|
-
expect(spy.called).to.be.false;
|
|
21
|
-
});
|
|
22
|
-
});
|
package/test/shared/mounting.tsx
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
// @ts-ignore-next-line
|
|
2
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
signal,
|
|
6
|
-
computed,
|
|
7
|
-
useComputed,
|
|
8
|
-
useSignal,
|
|
9
|
-
} from "@preact/signals-react";
|
|
10
|
-
import { expect } from "chai";
|
|
11
|
-
import { createElement, useReducer, StrictMode, useState } from "react";
|
|
12
|
-
|
|
13
|
-
import { getConsoleErrorSpy, checkConsoleErrorLogs } from "./utils";
|
|
14
|
-
|
|
15
|
-
export function mountSignalsTests(
|
|
16
|
-
render: (element: JSX.Element) => string | Promise<string>
|
|
17
|
-
) {
|
|
18
|
-
beforeEach(async () => {
|
|
19
|
-
getConsoleErrorSpy().resetHistory();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
checkConsoleErrorLogs();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe("mount text bindings", () => {
|
|
27
|
-
it("should render text without signals", async () => {
|
|
28
|
-
const html = await render(<span>test</span>);
|
|
29
|
-
expect(html).to.equal("<span>test</span>");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should render Signals as SignalValue", async () => {
|
|
33
|
-
const sig = signal("test");
|
|
34
|
-
const html = await render(<span>{sig}</span>);
|
|
35
|
-
expect(html).to.equal("<span>test</span>");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should render computed as SignalValue", async () => {
|
|
39
|
-
const sig = signal("test");
|
|
40
|
-
const comp = computed(() => `${sig} ${sig}`);
|
|
41
|
-
const html = await render(<span>{comp}</span>);
|
|
42
|
-
expect(html).to.equal("<span>test test</span>");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("mount component bindings", () => {
|
|
47
|
-
it("should mount component with signals as text", async () => {
|
|
48
|
-
const sig = signal("foo");
|
|
49
|
-
|
|
50
|
-
function App() {
|
|
51
|
-
const value = sig.value;
|
|
52
|
-
return <p>{value}</p>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const html = await render(<App />);
|
|
56
|
-
expect(html).to.equal("<p>foo</p>");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should activate signal accessed in render", async () => {
|
|
60
|
-
const sig = signal(null);
|
|
61
|
-
|
|
62
|
-
function App() {
|
|
63
|
-
const arr = useComputed(() => {
|
|
64
|
-
// trigger read
|
|
65
|
-
sig.value;
|
|
66
|
-
|
|
67
|
-
return [];
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const str = arr.value.join(", ");
|
|
71
|
-
return <p>{str}</p>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
await render(<App />);
|
|
76
|
-
} catch (e: any) {
|
|
77
|
-
expect.fail(e.stack);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("should properly mount in strict mode", async () => {
|
|
82
|
-
const sig = signal(-1);
|
|
83
|
-
|
|
84
|
-
const Test = () => <p>{sig.value}</p>;
|
|
85
|
-
const App = () => (
|
|
86
|
-
<StrictMode>
|
|
87
|
-
<Test />
|
|
88
|
-
</StrictMode>
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const html = await render(<App />);
|
|
92
|
-
expect(html).to.equal("<p>-1</p>");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should correctly mount components that have useReducer()", async () => {
|
|
96
|
-
const count = signal(0);
|
|
97
|
-
|
|
98
|
-
const Test = () => {
|
|
99
|
-
const [state] = useReducer((state: number, action: number) => {
|
|
100
|
-
return state + action;
|
|
101
|
-
}, -2);
|
|
102
|
-
|
|
103
|
-
const doubled = count.value * 2;
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<pre>
|
|
107
|
-
<code>{state}</code>
|
|
108
|
-
<code>{doubled}</code>
|
|
109
|
-
</pre>
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const html = await render(<Test />);
|
|
114
|
-
expect(html).to.equal("<pre><code>-2</code><code>0</code></pre>");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("should not fail when a component calls setState while mounting", async () => {
|
|
118
|
-
function App() {
|
|
119
|
-
const [state, setState] = useState(0);
|
|
120
|
-
if (state == 0) {
|
|
121
|
-
setState(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return <div>{state}</div>;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const html = await render(<App />);
|
|
128
|
-
expect(html).to.equal("<div>1</div>");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("should not fail when a component calls setState multiple times while mounting", async () => {
|
|
132
|
-
function App() {
|
|
133
|
-
const [state, setState] = useState(0);
|
|
134
|
-
if (state < 5) {
|
|
135
|
-
setState(state + 1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return <div>{state}</div>;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const html = await render(<App />);
|
|
142
|
-
expect(html).to.equal("<div>5</div>");
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("useSignal()", () => {
|
|
147
|
-
it("should create a signal from a primitive value", async () => {
|
|
148
|
-
function App() {
|
|
149
|
-
const count = useSignal(1);
|
|
150
|
-
return (
|
|
151
|
-
<div>
|
|
152
|
-
{count}
|
|
153
|
-
<button onClick={() => count.value++}>Increment</button>
|
|
154
|
-
</div>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const html = await render(<App />);
|
|
159
|
-
expect(html).to.equal("<div>1<button>Increment</button></div>");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("should properly update signal values changed during mount", async () => {
|
|
163
|
-
function App() {
|
|
164
|
-
const count = useSignal(0);
|
|
165
|
-
if (count.value == 0) {
|
|
166
|
-
count.value++;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<div>
|
|
171
|
-
{count}
|
|
172
|
-
<button onClick={() => count.value++}>Increment</button>
|
|
173
|
-
</div>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const html = await render(<App />);
|
|
178
|
-
expect(html).to.equal("<div>1<button>Increment</button></div>");
|
|
179
|
-
|
|
180
|
-
const html2 = await render(<App />);
|
|
181
|
-
expect(html2).to.equal("<div>1<button>Increment</button></div>");
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
}
|
package/test/shared/utils.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import sinon from "sinon";
|
|
3
|
-
import { act as realAct } from "react-dom/test-utils";
|
|
4
|
-
|
|
5
|
-
export interface Root {
|
|
6
|
-
render(element: JSX.Element | null): void;
|
|
7
|
-
unmount(): void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const isProd = process.env.NODE_ENV === "production";
|
|
11
|
-
export const isReact16 = React.version.startsWith("16.");
|
|
12
|
-
|
|
13
|
-
// We need to use createRoot() if it's available, but it's only available in
|
|
14
|
-
// React 18. To enable local testing with React 16 & 17, we'll create a fake
|
|
15
|
-
// createRoot() that uses render() and unmountComponentAtNode() instead.
|
|
16
|
-
let createRootCache: ((container: Element) => Root) | undefined;
|
|
17
|
-
export async function createRoot(container: Element): Promise<Root> {
|
|
18
|
-
if (!createRootCache) {
|
|
19
|
-
try {
|
|
20
|
-
// @ts-expect-error ESBuild will replace this import with a require() call
|
|
21
|
-
// if it resolves react-dom/client. If it doesn't, it will leave the
|
|
22
|
-
// import untouched causing a runtime error we'll handle below.
|
|
23
|
-
const { createRoot } = await import("react-dom/client");
|
|
24
|
-
createRootCache = createRoot;
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// @ts-expect-error ESBuild will replace this import with a require() call
|
|
27
|
-
// if it resolves react-dom.
|
|
28
|
-
const { render, unmountComponentAtNode } = await import("react-dom");
|
|
29
|
-
createRootCache = (container: Element) => ({
|
|
30
|
-
render(element: JSX.Element) {
|
|
31
|
-
render(element, container);
|
|
32
|
-
},
|
|
33
|
-
unmount() {
|
|
34
|
-
unmountComponentAtNode(container);
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return createRootCache(container);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// When testing using react's production build, we can't use act (React
|
|
44
|
-
// explicitly throws an error in this situation). So instead we'll fake act by
|
|
45
|
-
// waiting for a requestAnimationFrame and then 10ms for React's concurrent
|
|
46
|
-
// rerendering and any effects to flush. We'll make a best effort to throw a
|
|
47
|
-
// helpful error in afterEach if we detect that act() was called but not
|
|
48
|
-
// awaited.
|
|
49
|
-
const afterFrame = (ms: number) =>
|
|
50
|
-
new Promise(r => requestAnimationFrame(() => setTimeout(r, ms)));
|
|
51
|
-
|
|
52
|
-
let acting = 0;
|
|
53
|
-
async function prodActShim(cb: () => void | Promise<void>): Promise<void> {
|
|
54
|
-
acting++;
|
|
55
|
-
try {
|
|
56
|
-
await cb();
|
|
57
|
-
await afterFrame(10);
|
|
58
|
-
} finally {
|
|
59
|
-
acting--;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function checkHangingAct() {
|
|
64
|
-
if (acting > 0) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`It appears act() was called but not awaited. This could happen if a test threw an Error or if a test forgot to await a call to act. Make sure to await act() calls in tests.`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const act =
|
|
72
|
-
process.env.NODE_ENV === "production"
|
|
73
|
-
? (prodActShim as typeof realAct)
|
|
74
|
-
: realAct;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* `console.log` supports formatting strings with `%s` for string substitutions.
|
|
78
|
-
* This function accepts a string and additional arguments of values and returns
|
|
79
|
-
* a string with the values substituted in.
|
|
80
|
-
*/
|
|
81
|
-
export function consoleFormat(str: string, ...values: unknown[]): string {
|
|
82
|
-
let idx = 0;
|
|
83
|
-
return str.replace(/%s/g, () => String(values[idx++]));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
declare global {
|
|
87
|
-
let errorSpy: sinon.SinonSpy | undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Only one spy can be active on an object at a time and since all tests share
|
|
91
|
-
// the same console object we need to make sure we're only spying on it once.
|
|
92
|
-
// We'll use this method to share the spy across all tests.
|
|
93
|
-
export function getConsoleErrorSpy(): sinon.SinonSpy {
|
|
94
|
-
if (typeof errorSpy === "undefined") {
|
|
95
|
-
(globalThis as any).errorSpy = sinon.spy(console, "error");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return errorSpy!;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const messagesToIgnore = [
|
|
102
|
-
// Ignore errors for timeouts of tests that often happen while debugging
|
|
103
|
-
/async tests and hooks,/,
|
|
104
|
-
// Ignore React 16 warnings about awaiting `act` calls (warning removed in React 18)
|
|
105
|
-
/Do not await the result of calling act/,
|
|
106
|
-
// Ignore how chai or mocha uses `console.error` to print out errors
|
|
107
|
-
/AssertionError/,
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
export function checkConsoleErrorLogs(): void {
|
|
111
|
-
const errorSpy = getConsoleErrorSpy();
|
|
112
|
-
if (errorSpy.called) {
|
|
113
|
-
let message: string;
|
|
114
|
-
if (errorSpy.firstCall.args[0].toString().includes("%s")) {
|
|
115
|
-
const firstArg = errorSpy.firstCall.args[0];
|
|
116
|
-
message = consoleFormat(firstArg, ...errorSpy.firstCall.args.slice(1));
|
|
117
|
-
} else {
|
|
118
|
-
message = errorSpy.firstCall.args.join(" ");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (messagesToIgnore.every(re => re.test(message) === false)) {
|
|
122
|
-
expect.fail(
|
|
123
|
-
`Console.error was unexpectedly called with this message: \n${message}`
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|