Create signal implementations that satisfies FrameworkSignal. Simplify SignalKinds.#1017
Create signal implementations that satisfies FrameworkSignal. Simplify SignalKinds.#1017ditman wants to merge 3 commits intogoogle:avac-generic-signalsfrom
satisfies FrameworkSignal. Simplify SignalKinds.#1017Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the FrameworkSignal abstraction and its type-mapping system to use conditional types, simplifying how different signal implementations are integrated. It also introduces a shared test suite and specific implementations for Angular and Preact signals. The review feedback identifies that the effect implementations for both Angular and Preact do not correctly handle the cleanup callback and recommends adding comprehensive test coverage for the effect function within the shared testing utility.
| effect: (fn: () => void, cleanupCallback: () => void) => { | ||
| const e = effect(cleanupRegisterFn => { | ||
| cleanupRegisterFn(cleanupCallback); | ||
| fn(); | ||
| }); | ||
| return () => e.destroy(); | ||
| }, |
There was a problem hiding this comment.
The effect implementation doesn't correctly match the FrameworkSignal interface. The cleanupCallback is defined as optional in the interface (cleanupCallback?: () => void), but it's required in this implementation. This will cause a type error and runtime issues if effect is called without a cleanup callback.
Please update the implementation to handle an optional cleanupCallback.
effect: (fn: () => void, cleanupCallback?: () => void) => {
const e = effect(cleanupRegisterFn => {
if (cleanupCallback) {
cleanupRegisterFn(cleanupCallback);
}
fn();
});
return () => e.destroy();
},| wrap: <T>(val: T) => new PSignal(val), | ||
| unwrap: <T>(val: PSignal<T>) => val.value, | ||
| set: <T>(sig: PSignal<T>, value: T) => (sig.value = value), | ||
| effect: (fn: () => void) => effect(fn), |
There was a problem hiding this comment.
The current implementation of effect for Preact signals doesn't support the cleanupCallback from the FrameworkSignal interface. It completely ignores the second argument.
The effect function from @preact/signals-core supports a cleanup mechanism by allowing the function passed to it to return a cleanup function. You should adapt the implementation to use this feature.
effect: (fn: () => void, cleanupCallback?: () => void) => effect(() => {
fn();
return cleanupCallback;
})| it('rejects a non-signal', () => { | ||
| assert.strictEqual(SignalImpl.isSignal('hello'), false); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The shared tests for FrameworkSignal implementations are missing coverage for the effect function. According to the repository's style guide, code changes should have tests. Since the effect function is a key part of the FrameworkSignal interface, it should be tested to ensure all implementations behave correctly, especially regarding cleanup logic.
I suggest adding a test case for effect.
});
it('handles effects and cleanup', () => {
const signal = SignalImpl.wrap(0);
let effectCount = 0;
let cleanupCount = 0;
const stopEffect = SignalImpl.effect(
() => {
SignalImpl.unwrap(signal); // depend on signal
effectCount++;
},
() => {
cleanupCount++;
}
);
assert.strictEqual(effectCount, 1, 'effect should run once on creation');
assert.strictEqual(cleanupCount, 0, 'cleanup should not run on creation');
SignalImpl.set(signal, 1);
// Effect should run again, and cleanup should run once before it.
assert.strictEqual(effectCount, 2, 'effect should re-run on dependency change');
assert.strictEqual(cleanupCount, 1, 'cleanup should run before effect re-run');
stopEffect();
// Cleanup should run on stop.
assert.strictEqual(cleanupCount, 2, 'cleanup should run on stop');
SignalImpl.set(signal, 2);
// Effect and cleanup should not run after stopping.
assert.strictEqual(effectCount, 2, 'effect should not run after stop');
assert.strictEqual(cleanupCount, 2, 'cleanup should not run after stop');
});
Description
Make SignalKinds just a holder for "readonly" and "writable" signal types, and use that in the FrameworkSignal type.
Users need to define their FrameworkSignal type as
satisfies FrameworkSignalto prevent TS from erasing the internal types (similar to what we do with the component props!).Split tests into 3 files: 2 for per-framework initialization, and a common one that runs the same tests across both implementations. The verification code remains the same as in the OG commit.
There's some shenanigans with tsc merging all our test files together and seeing the Preact and Angular types colliding with each other, and tests are not exactly identical to what users would define, but this is the general idea.
(I don't know if this does everything we want though!)
Pre-launch Checklist
If you need help, consider asking for advice on the discussion board.