diff --git a/.changeset/range-solid2-migration.md b/.changeset/range-solid2-migration.md new file mode 100644 index 000000000..1349fa1c9 --- /dev/null +++ b/.changeset/range-solid2-migration.md @@ -0,0 +1,16 @@ +--- +"@solid-primitives/range": major +--- + +Migrate to Solid.js v2.0 (beta.13) + +## Breaking Changes + +**Peer dependencies**: `solid-js@^2.0.0-beta.13` and `@solidjs/web@^2.0.0-beta.13` are now required. + +- `isServer` is now imported from `@solidjs/web` (was `solid-js/web`) +- `JSX` types are now imported from `@solidjs/web` (was `solid-js`) +- `indexRange` internal signals now use `ownedWrite: true` so they can be updated from inside reactive scopes (e.g. when `IndexRange` wraps in `createMemo`) +- `createEffect` usage in user code must use the split compute/apply form required by Solid 2.0 — see updated README examples +- Signal writes are now batched by default; call `flush()` after writing signals before reading the resulting mapped array +- `` is **deprecated** — Solid 2.0 ships an equivalent built-in `` in `@solidjs/web`. See the README for migration guidance. The `repeat` primitive continues to be supported for cases that require incremental child creation/disposal. diff --git a/packages/range/README.md b/packages/range/README.md index 2506e6abc..97bcb4f03 100644 --- a/packages/range/README.md +++ b/packages/range/README.md @@ -10,8 +10,9 @@ Control Flow Primitives for displaying a number range or given number of elements. +- [`createNumericRange`](#createnumericrange) - Reactively generates a number array for a given range. Convenient companion to the built-in ``. - [`repeat`](#repeat) - Primitive for mapping a number of elements. Underlying helper for the [``](#repeat-1) control flow. -- [``](#repeat-1) - Control Flow Component for displaying a number of elements. +- [``](#repeat-1) - Control Flow Component for displaying a number of elements. ⚠️ Deprecated — use Solid 2.0's built-in ``. - [`mapRange`](#maprange) - Primitive for mapping a number range of given start, end, and step values. Underlying helper for the [``](#range) control flow. - [``](#range) - Control Flow Component for displaying a number range of elements. - [`indexRange`](#indexrange) - Primitive for mapping a number range while keeping previous elements of the same index. Underlying helper for the [``](#indexrange-1) control flow. @@ -25,15 +26,50 @@ npm install @solid-primitives/range yarn add @solid-primitives/range ``` +Requires `solid-js` and `@solidjs/web` v2.0.0-beta.13 or later as peer dependencies. + +## `createNumericRange` + +Reactively generates an array of numbers for the given range. Mirrors the `range()` API from Python — one argument gives `[0, to)`, two or three give `[start, to)` with an optional step. + +All arguments accept either a plain number or a reactive accessor. Pairs naturally with Solid 2.0's built-in ``. + +```ts +// static +createNumericRange(5) // [0, 1, 2, 3, 4] +createNumericRange(2, 7) // [2, 3, 4, 5, 6] +createNumericRange(0, 10, 2) // [0, 2, 4, 6, 8] +createNumericRange(5, 0) // [5, 4, 3, 2, 1] (descending) + +// reactive +const [to, setTo] = createSignal(5); +const nums = createNumericRange(to); // updates when `to` changes + +// compose with — pass the signal, not the called value +const nums = createNumericRange(count); +{n =>
{n}
}
+``` + +#### Definition + +```ts +function createNumericRange(to: MaybeAccessor): Accessor; +function createNumericRange( + start: MaybeAccessor, + to: MaybeAccessor, + step?: MaybeAccessor, +): Accessor; +``` + ## `repeat` -Reactively maps a number range of specified length with a callback function - underlying helper for the [``](#repeat-1) control flow. +Reactively maps a number range of specified length with a callback function - underlying helper for the `` control flow. ```ts const [length, setLength] = createSignal(10) const mapped = repeat(length, index => { const [value, setValue] = createSignal(index); - createEffect(() => {...}) + createEffect(() => value(), v => { /* react to value */ }) return value }) ``` @@ -52,6 +88,25 @@ function repeat( ## `` +> **Deprecated** — Solid 2.0 ships a built-in `` in `@solidjs/web`. Migrate to that: +> +> ```tsx +> import { Repeat } from "@solidjs/web"; +> +> // basic — children receive a plain index number +> {(i) =>
{i}
}
+> +> // with offset (replaces a start > 0 use-case) +> {(i) =>
{i}
}
+> +> // with fallback — wrap in since built-in has no fallback prop +> 0} fallback={

no items

}> +> {(i) =>
{i}
}
+>
+> ``` +> +> **Note:** The built-in `` does not diff — it re-renders all children when `count` changes. If incremental creation/disposal of children is important for your use case, keep using this primitive. + Control Flow component for displaying a specified number of elements. The `times` prop is reactive – changing it will only create new elements for added numbers. @@ -84,9 +139,9 @@ function Repeat(props: { ## `mapRange` -Reactively maps a number range of specified `stop`, `to` and `step`, with a callback function - underlying helper for the [``](#range) control flow. +Reactively maps a number range of specified `start`, `to` and `step`, with a callback function - underlying helper for the [``](#range) control flow. -All `stop`, `to` and `step` arguments are accessors, and changing them will cause the mapped array to be recalculated, mapping new items for numbers added to the range. +All `start`, `to` and `step` arguments are accessors, and changing them will cause the mapped array to be recalculated, mapping new items for numbers added to the range. `step` will become negative _(the range will be descending)_ if `to` is smaller than `start`. Range stops at `to`, it is not included in the range. @@ -94,7 +149,7 @@ All `stop`, `to` and `step` arguments are accessors, and changing them will caus const [to, setTo] = createSignal(5) const mapped = mapRange(() => 0, to, () => 0.5, number => { const [value, setValue] = createSignal(number); - createEffect(() => {...}) + createEffect(() => value(), v => { /* react to value */ }) return value }) mapped() // => [0, 0.5, 1, 1.5, 2...] @@ -119,7 +174,7 @@ function mapRange( Creates a list of elements by mapping a number range of specified `start`, `to`, and `step`. -All `stop`, `to` and `step` props are reactive, and changing them will cause the elements array to be recalculated, creating new elements for numbers added to the range. +All `start`, `to` and `step` props are reactive, and changing them will cause the elements array to be recalculated, creating new elements for numbers added to the range. - `start` defaults to 0. @@ -157,7 +212,7 @@ const [step, setStep] = createSignal(2); #### Definition -`RangeProps` is an interface of `stop`, `to` and `step` props, OR `0`, `1` and `2` indexes of a spread array. +`RangeProps` is an interface of `start`, `to` and `step` props, OR `0`, `1` and `2` indexes of a spread array. ```ts function Range( @@ -170,9 +225,9 @@ function Range( ## `indexRange` -Primitive for mapping a number range of specified `stop`, `to` and `step`, while keeping previous elements of the same index. Underlying helper for the [``](#indexrange-1) control flow. +Primitive for mapping a number range of specified `start`, `to` and `step`, while keeping previous elements of the same index. Underlying helper for the [``](#indexrange-1) control flow. -All `stop`, `to` and `step` arguments are accessors, and changing them will cause the mapped array to be recalculated, mapping new items appended at the end of the range. +All `start`, `to` and `step` arguments are accessors, and changing them will cause the mapped array to be recalculated, mapping new items appended at the end of the range. `step` will become negative _(the range will be descending)_ if `to` is smaller than `start`. Range stops at `to`, it is not included in the range. @@ -184,7 +239,7 @@ const mapped = indexRange( () => 0.5, number => { const [value, setValue] = createSignal(number()); - createEffect(() => handleNewNumber(number())); + createEffect(() => number(), n => handleNewNumber(n)); return value; }, ); @@ -210,7 +265,7 @@ function indexRange( Control Flow Component for displaying a number range of elements, where elements receive a number value as signal, by mapping a number range of specified `start`, `to`, and `step`. -All `stop`, `to` and `step` props are reactive, and changing them will cause the elements array to be recalculated, creating new elements for numbers added to the range. +All `start`, `to` and `step` props are reactive, and changing them will cause the elements array to be recalculated, creating new elements for numbers added to the range. - `start` defaults to 0. @@ -248,7 +303,7 @@ const [step, setStep] = createSignal(2); #### Definition -`RangeProps` is an interface of `stop`, `to` and `step` props, OR `0`, `1` and `2` indexes of a spread array. +`RangeProps` is an interface of `start`, `to` and `step` props, OR `0`, `1` and `2` indexes of a spread array. ```ts function IndexRange( diff --git a/packages/range/package.json b/packages/range/package.json index 6ea0885b6..1190fb58a 100644 --- a/packages/range/package.json +++ b/packages/range/package.json @@ -1,7 +1,7 @@ { "name": "@solid-primitives/range", "version": "0.2.4", - "description": "Control Flow Primitives for displaying given number or a number range of elements.", + "description": "Control Flow Primitives for number ranges: createNumericRange, mapRange, indexRange, repeat, and their JSX component counterparts.", "author": "Damian Tarnawski @thetarnav ", "contributors": [], "license": "MIT", @@ -17,11 +17,11 @@ "name": "range", "stage": 1, "list": [ + "createNumericRange", "repeat", "mapRange", "indexRange", "Repeat", - "Range", "IndexRange" ], "category": "Control Flow" @@ -57,14 +57,15 @@ "test:ssr": "pnpm run vitest --mode ssr" }, "devDependencies": { - "solid-js": "^1.9.7", - "solid-transition-group": "^0.2.3" + "@solidjs/web": "2.0.0-beta.13", + "solid-js": "2.0.0-beta.13" }, "dependencies": { "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.13", + "solid-js": "^2.0.0-beta.13" }, "typesVersions": {} } diff --git a/packages/range/src/createNumericRange.ts b/packages/range/src/createNumericRange.ts new file mode 100644 index 000000000..d6f0fd5f4 --- /dev/null +++ b/packages/range/src/createNumericRange.ts @@ -0,0 +1,44 @@ +import type { Accessor } from "solid-js"; +import type { MaybeAccessor } from "@solid-primitives/utils"; +import { access } from "@solid-primitives/utils"; +import { mapRange } from "./mapRange.js"; + +/** + * Reactively generates an array of numbers for the given range. + * + * When called with one argument, generates `[0, 1, ..., to - 1]`. + * When called with two or three arguments, generates `[start, start + step, ..., to - 1]`. + * + * Step direction is inferred from the relationship between `start` and `to` — a negative + * `to` relative to `start` produces a descending range regardless of the sign of `step`. + * + * @param startOrTo start of the range, or — when `to` is omitted — the end (start defaults to 0) + * @param to end of the range (not included) + * @param step difference between consecutive values (defaults to 1) + * @returns accessor returning the current number array + * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#createnumericrange + * @example + * ```ts + * const nums = createNumericRange(5); // [0, 1, 2, 3, 4] + * const nums = createNumericRange(2, 7); // [2, 3, 4, 5, 6] + * const nums = createNumericRange(0, 10, 2); // [0, 2, 4, 6, 8] + * + * // reactive + * const [to, setTo] = createSignal(5); + * const nums = createNumericRange(to); // updates when `to` changes + * + * // use with + * const nums = createNumericRange(count); + * {n =>
{n}
}
+ * ``` + */ +export function createNumericRange( + startOrTo: MaybeAccessor, + to?: MaybeAccessor, + step: MaybeAccessor = 1, +): Accessor { + const getStart = to !== undefined ? () => access(startOrTo) : () => 0; + const getTo = to !== undefined ? () => access(to) : () => access(startOrTo); + const getStep = () => access(step); + return mapRange(getStart, getTo, getStep, n => n); +} diff --git a/packages/range/src/index.ts b/packages/range/src/index.ts index f140ff41d..2f1c2cff0 100644 --- a/packages/range/src/index.ts +++ b/packages/range/src/index.ts @@ -2,3 +2,4 @@ export { type RangeProps } from "./common.js"; export * from "./repeat.js"; export * from "./mapRange.js"; export * from "./indexRange.js"; +export * from "./createNumericRange.js"; diff --git a/packages/range/src/indexRange.ts b/packages/range/src/indexRange.ts index 6c43fbb33..a8f949ad7 100644 --- a/packages/range/src/indexRange.ts +++ b/packages/range/src/indexRange.ts @@ -6,10 +6,10 @@ import { type Setter, untrack, DEV, - type JSX, createMemo, } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer, type JSX } from "@solidjs/web"; +import { INTERNAL_OPTIONS } from "@solid-primitives/utils"; import { abs, ceil, min, type RangeProps, sign, toFunction, accessor } from "./common.js"; /** @@ -49,7 +49,7 @@ export function indexRange( const mapper = (i: number, n: number): void => createRoot(dispose => { - const [number, setNumber] = createSignal(n); + const [number, setNumber] = createSignal(n, INTERNAL_OPTIONS); disposers[i] = dispose; items[i] = mapFn(number); setters[i] = setNumber; diff --git a/packages/range/src/mapRange.ts b/packages/range/src/mapRange.ts index 9102cd32e..e6ad7de7a 100644 --- a/packages/range/src/mapRange.ts +++ b/packages/range/src/mapRange.ts @@ -1,5 +1,5 @@ -import { type Accessor, createRoot, onCleanup, untrack, DEV, type JSX, createMemo } from "solid-js"; -import { isServer } from "solid-js/web"; +import { type Accessor, createRoot, onCleanup, untrack, DEV, createMemo } from "solid-js"; +import { isServer, type JSX } from "@solidjs/web"; import { abs, accessor, ceil, floor, min, type RangeProps, toFunction } from "./common.js"; /** diff --git a/packages/range/src/repeat.ts b/packages/range/src/repeat.ts index a9b2c3166..90bed021c 100644 --- a/packages/range/src/repeat.ts +++ b/packages/range/src/repeat.ts @@ -1,4 +1,5 @@ -import { type Accessor, type JSX, createMemo, createRoot, onCleanup } from "solid-js"; +import { type Accessor, createMemo, createRoot, onCleanup } from "solid-js"; +import { type JSX } from "@solidjs/web"; import { toFunction } from "./common.js"; /** @@ -8,15 +9,6 @@ import { toFunction } from "./common.js"; * @param options a fallback for when the input list is empty or missing * @returns mapped input array signal * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#repeat - * @example - * ```tsx - * const [length, setLength] = createSignal(10) - * const mapped = repeat(length, index => { - * const [value, setValue] = createSignal(index); - * createEffect(() => {...}) - * return value - * }) - * ``` */ export function repeat( times: Accessor, @@ -89,16 +81,25 @@ export function repeat( } /** - * Creates a range of elements `of` specified size. + * Creates a range of elements of specified size. * @param times number of elements - * @param fallback element returned when `of` equals 0 + * @param fallback element returned when `times` equals 0 * @param children render function * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#Repeat-1 - * @example + * @deprecated Use the built-in `` from `@solidjs/web` instead: * ```tsx - * - * {n =>
{n}
} - *
+ * import { Repeat } from "@solidjs/web"; + * + * // basic + * {(i) =>
{i}
}
+ * + * // with offset + * {(i) =>
{i}
}
+ * + * // with fallback + * 0} fallback={

no items

}> + * {(i) =>
{i}
}
+ *
* ``` */ export function Repeat(props: { diff --git a/packages/range/test/createNumericRange.test.ts b/packages/range/test/createNumericRange.test.ts new file mode 100644 index 000000000..7a90dc2c2 --- /dev/null +++ b/packages/range/test/createNumericRange.test.ts @@ -0,0 +1,79 @@ +import { expect, describe, it } from "vitest"; +import { createRoot, createSignal, flush } from "solid-js"; +import { createNumericRange } from "../src/index.js"; + +describe("createNumericRange", () => { + it("single argument: [0, to)", () => + createRoot(dispose => { + expect(createNumericRange(5)()).toEqual([0, 1, 2, 3, 4]); + expect(createNumericRange(1)()).toEqual([0]); + expect(createNumericRange(0)()).toEqual([]); + dispose(); + })); + + it("two arguments: [start, to)", () => + createRoot(dispose => { + expect(createNumericRange(2, 5)()).toEqual([2, 3, 4]); + expect(createNumericRange(0, 3)()).toEqual([0, 1, 2]); + expect(createNumericRange(3, 3)()).toEqual([]); + dispose(); + })); + + it("three arguments: [start, to) by step", () => + createRoot(dispose => { + expect(createNumericRange(0, 10, 2)()).toEqual([0, 2, 4, 6, 8]); + expect(createNumericRange(1, 10, 3)()).toEqual([1, 4, 7]); + expect(createNumericRange(0, 1, 0.5)()).toEqual([0, 0.5]); + dispose(); + })); + + it("descending range", () => + createRoot(dispose => { + expect(createNumericRange(5, 0)()).toEqual([5, 4, 3, 2, 1]); + expect(createNumericRange(10, 4, 2)()).toEqual([10, 8, 6]); + dispose(); + })); + + it("reactive single argument", () => { + const [to, setTo] = createSignal(3); + const [dispose, nums] = createRoot(dispose => [dispose, createNumericRange(to)] as const); + + expect(nums()).toEqual([0, 1, 2]); + setTo(5); + flush(); + expect(nums()).toEqual([0, 1, 2, 3, 4]); + setTo(1); + flush(); + expect(nums()).toEqual([0]); + + dispose(); + }); + + it("reactive start and to", () => { + const [start, setStart] = createSignal(2); + const [to, setTo] = createSignal(6); + const [dispose, nums] = createRoot(dispose => [dispose, createNumericRange(start, to)] as const); + + expect(nums()).toEqual([2, 3, 4, 5]); + setStart(4); + flush(); + expect(nums()).toEqual([4, 5]); + setTo(8); + flush(); + expect(nums()).toEqual([4, 5, 6, 7]); + + dispose(); + }); + + it("reactive step", () => { + const [step, setStep] = createSignal(1); + const [dispose, nums] = createRoot(dispose => [dispose, createNumericRange(0, 6, step)] as const); + + expect(nums()).toEqual([0, 1, 2, 3, 4, 5]); + setStep(2); + flush(); + expect(nums()).toEqual([0, 2, 4]); + + dispose(); + }); +}); diff --git a/packages/range/test/indexRange.test.ts b/packages/range/test/indexRange.test.ts index 396d51031..bc4735bb8 100644 --- a/packages/range/test/indexRange.test.ts +++ b/packages/range/test/indexRange.test.ts @@ -1,5 +1,5 @@ import { expect, describe, it } from "vitest"; -import { createRoot, createSignal, onCleanup } from "solid-js"; +import { createRoot, createSignal, flush, onCleanup } from "solid-js"; import { indexRange } from "../src/index.js"; describe("indexRange", () => { @@ -54,117 +54,134 @@ describe("indexRange", () => { dispose(); })); - it("updates to correct array", () => - createRoot(dispose => { - const [start, setStart] = createSignal(-3.5); - const [to, setTo] = createSignal(0.2); - const [step, setStep] = createSignal(1.5); - - const mapped = indexRange(start, to, step, n => n); - - const a = mapped(); - expect(a.length).toBe(3); - expect(a[0]?.()).toBe(-3.5); - expect(a[1]?.()).toBe(-2); - expect(a[2]?.()).toBe(-0.5); - - setStart(0); - setTo(2); - setStep(0.2); - const b = mapped(); - for (let n = 0, i = 0; i < 10; n += 0.2, i++) { - expect(b[i]?.()).toBe(n); - } - - setStart(5); - setTo(-5); - setStep(2); - const c = mapped(); - expect(c.length).toBe(5); - expect(c[0]?.()).toBe(5); - expect(c[1]?.()).toBe(3); - expect(c[2]?.()).toBe(1); - expect(c[3]?.()).toBe(-1); - expect(c[4]?.()).toBe(-3); - - dispose(); - })); - - it("maps only added indexes", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, _setStep] = createSignal(1); - - const captured: (string | number)[] = []; - const mapped = indexRange(start, to, step, n => { + it("updates to correct array", () => { + const [start, setStart] = createSignal(-3.5); + const [to, setTo] = createSignal(0.2); + const [step, setStep] = createSignal(1.5); + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + indexRange(start, to, step, n => n), + ] as const); + + const a = mapped(); + expect(a.length).toBe(3); + expect(a[0]?.()).toBe(-3.5); + expect(a[1]?.()).toBe(-2); + expect(a[2]?.()).toBe(-0.5); + + setStart(0); + setTo(2); + setStep(0.2); + flush(); + const b = mapped(); + flush(); // apply batched setter writes for existing items + for (let n = 0, i = 0; i < 10; n += 0.2, i++) { + expect(b[i]?.()).toBe(n); + } + + setStart(5); + setTo(-5); + setStep(2); + flush(); + const c = mapped(); + flush(); // apply batched setter writes for existing items + expect(c.length).toBe(5); + expect(c[0]?.()).toBe(5); + expect(c[1]?.()).toBe(3); + expect(c[2]?.()).toBe(1); + expect(c[3]?.()).toBe(-1); + expect(c[4]?.()).toBe(-3); + + dispose(); + }); + + it("maps only added indexes", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step] = createSignal(1); + const captured: (string | number)[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + indexRange(start, to, step, n => { captured.push(n()); - }); - - mapped(); - expect(captured).toEqual([4, 5, 6, 7]); - - setStart(6); - setTo(9); - mapped(); - expect(captured).toEqual([4, 5, 6, 7]); - - setStart(4); - setTo(10); - mapped(); - expect(captured).toEqual([4, 5, 6, 7, 7, 8, 9]); - - dispose(); - })); - - it("disposes on remove and cleanup", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, setStep] = createSignal(1); - - const captured: (string | number)[] = []; - const mapped = indexRange(start, to, step, n => { + }), + ] as const); + + mapped(); + expect(captured).toEqual([4, 5, 6, 7]); + + setStart(6); + setTo(9); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 6, 7]); + + setStart(4); + setTo(10); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 6, 7, 7, 8, 9]); + + dispose(); + }); + + it("disposes on remove and cleanup", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step, setStep] = createSignal(1); + const captured: (string | number)[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + indexRange(start, to, step, n => { onCleanup(() => captured.push(n())); - }); - - mapped(); - expect(captured).toEqual([]); - - setStart(6); - setTo(9); - mapped(); - expect(captured).toEqual([7]); - - setStart(6); - setTo(9); - mapped(); - expect(captured).toEqual([7]); - - setStep(1.5); - mapped(); - expect(captured).toEqual([7, 8]); - - dispose(); - })); - - it("displays a fallback", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, _setStep] = createSignal(1); - - const mapped = indexRange(start, to, step, n => n(), { - fallback: () => "fb", - }); - - setStart(0); - setTo(0); - expect(mapped()).toEqual(["fb"]); - - setTo(2); - expect(mapped()).toEqual([0, 1]); - - dispose(); - })); + }), + ] as const); + + mapped(); + expect(captured).toEqual([]); + + setStart(6); + setTo(9); + flush(); + mapped(); + expect(captured).toEqual([7]); + + setStart(6); + setTo(9); + flush(); + mapped(); + expect(captured).toEqual([7]); + + setStep(1.5); + flush(); + mapped(); + expect(captured).toEqual([7, 8]); + + dispose(); + }); + + it("displays a fallback", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step] = createSignal(1); + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + indexRange(start, to, step, n => n(), { fallback: () => "fb" }), + ] as const); + + setStart(0); + setTo(0); + flush(); + expect(mapped()).toEqual(["fb"]); + + setTo(2); + flush(); + expect(mapped()).toEqual([0, 1]); + + dispose(); + }); }); diff --git a/packages/range/test/mapRange.test.ts b/packages/range/test/mapRange.test.ts index eaed3c933..a4c5995d5 100644 --- a/packages/range/test/mapRange.test.ts +++ b/packages/range/test/mapRange.test.ts @@ -1,5 +1,5 @@ import { expect, describe, it } from "vitest"; -import { createRoot, createSignal, onCleanup } from "solid-js"; +import { createRoot, createSignal, flush, onCleanup } from "solid-js"; import { mapRange } from "../src/index.js"; describe("mapRange", () => { @@ -54,111 +54,126 @@ describe("mapRange", () => { dispose(); })); - it("updates to correct array", () => - createRoot(dispose => { - const [start, setStart] = createSignal(-3.5); - const [to, setTo] = createSignal(0.2); - const [step, setStep] = createSignal(1.5); - - const mapped = mapRange(start, to, step, n => n); - - expect(mapped()).toEqual([-3.5, -2, -0.5]); - - setStart(0); - setTo(2); - setStep(0.2); - const a = mapped(); - for (let n = 0, i = 0; i < 10; n += 0.2, i++) { - expect(a[i]).toBe(n); - } - - setStart(5); - setTo(-5); - setStep(2); - expect(mapped()).toEqual([5, 3, 1, -1, -3]); - - dispose(); - })); - - it("maps only added numbers", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, setStep] = createSignal(1); - - const captured: (string | number)[] = []; - const mapped = mapRange(start, to, step, n => { + it("updates to correct array", () => { + const [start, setStart] = createSignal(-3.5); + const [to, setTo] = createSignal(0.2); + const [step, setStep] = createSignal(1.5); + + const [dispose, mapped] = createRoot(dispose => [dispose, mapRange(start, to, step, n => n)] as const); + + expect(mapped()).toEqual([-3.5, -2, -0.5]); + + setStart(0); + setTo(2); + setStep(0.2); + flush(); + const a = mapped(); + for (let n = 0, i = 0; i < 10; n += 0.2, i++) { + expect(a[i]).toBe(n); + } + + setStart(5); + setTo(-5); + setStep(2); + flush(); + expect(mapped()).toEqual([5, 3, 1, -1, -3]); + + dispose(); + }); + + it("maps only added numbers", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step, setStep] = createSignal(1); + const captured: (string | number)[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + mapRange(start, to, step, n => { captured.push(n); - }); - - mapped(); - expect(captured).toEqual([4, 5, 6, 7]); - - setStart(6); - setTo(9); - mapped(); - expect(captured).toEqual([4, 5, 6, 7, 8]); - - setStart(4); - setTo(7); - mapped(); - expect(captured).toEqual([4, 5, 6, 7, 8, 4, 5]); - - setStart(3); - setStep(1.5); - mapped(); - expect(captured).toEqual([4, 5, 6, 7, 8, 4, 5, 3, 4.5]); - - dispose(); - })); - - it("disposes on remove and cleanup", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, setStep] = createSignal(1); - - const captured: (string | number)[] = []; - const mapped = mapRange(start, to, step, n => { + }), + ] as const); + + mapped(); + expect(captured).toEqual([4, 5, 6, 7]); + + setStart(6); + setTo(9); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 6, 7, 8]); + + setStart(4); + setTo(7); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 6, 7, 8, 4, 5]); + + setStart(3); + setStep(1.5); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 6, 7, 8, 4, 5, 3, 4.5]); + + dispose(); + }); + + it("disposes on remove and cleanup", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step, setStep] = createSignal(1); + const captured: (string | number)[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + mapRange(start, to, step, n => { onCleanup(() => captured.push(n)); - }); - - mapped(); - expect(captured).toEqual([]); - - setStart(6); - setTo(9); - mapped(); - expect(captured).toEqual([4, 5]); - - setStart(4); - setTo(7); - mapped(); - expect(captured).toEqual([4, 5, 7, 8]); - - setStart(3); - setStep(1.5); - mapped(); - expect(captured).toEqual([4, 5, 7, 8, 4, 5]); - - dispose(); - })); - - it("displays a fallback", () => - createRoot(dispose => { - const [start, setStart] = createSignal(4); - const [to, setTo] = createSignal(8); - const [step, _setStep] = createSignal(1); - - const mapped = mapRange(start, to, step, n => n, { fallback: () => "fb" }); - - setStart(0); - setTo(0); - expect(mapped()).toEqual(["fb"]); - - setTo(2); - expect(mapped()).toEqual([0, 1]); - - dispose(); - })); + }), + ] as const); + + mapped(); + expect(captured).toEqual([]); + + setStart(6); + setTo(9); + flush(); + mapped(); + expect(captured).toEqual([4, 5]); + + setStart(4); + setTo(7); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 7, 8]); + + setStart(3); + setStep(1.5); + flush(); + mapped(); + expect(captured).toEqual([4, 5, 7, 8, 4, 5]); + + dispose(); + }); + + it("displays a fallback", () => { + const [start, setStart] = createSignal(4); + const [to, setTo] = createSignal(8); + const [step] = createSignal(1); + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + mapRange(start, to, step, n => n, { fallback: () => "fb" }), + ] as const); + + setStart(0); + setTo(0); + flush(); + expect(mapped()).toEqual(["fb"]); + + setTo(2); + flush(); + expect(mapped()).toEqual([0, 1]); + + dispose(); + }); }); diff --git a/packages/range/test/repeat.test.ts b/packages/range/test/repeat.test.ts index 1fc3438b9..c621377eb 100644 --- a/packages/range/test/repeat.test.ts +++ b/packages/range/test/repeat.test.ts @@ -1,35 +1,44 @@ import { expect, describe, it } from "vitest"; -import { createComputed, createRoot, createSignal, onCleanup } from "solid-js"; +import { createEffect, createRoot, createSignal, flush, onCleanup } from "solid-js"; import { Repeat, repeat } from "../src/index.js"; describe("repeat", () => { - it("maps only added items", () => - createRoot(dispose => { - const [length, setLength] = createSignal(5); - const captured: number[] = []; - const mapped = repeat(length, i => { + it("maps only added items", () => { + const [length, setLength] = createSignal(5); + const captured: number[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + repeat(length, i => { captured.push(i); return i; - }); - expect(mapped(), "initial mapped").toEqual([0, 1, 2, 3, 4]); - expect(captured, "initial captured").toEqual([0, 1, 2, 3, 4]); - setLength(7); - expect(mapped(), "1 mapped").toEqual([0, 1, 2, 3, 4, 5, 6]); - expect(captured, "1 captured").toEqual([0, 1, 2, 3, 4, 5, 6]); - setLength(3); - expect(mapped(), "2 mapped").toEqual([0, 1, 2]); - expect(captured, "2 captured").toEqual([0, 1, 2, 3, 4, 5, 6]); - setLength(5); - expect(mapped(), "3 mapped").toEqual([0, 1, 2, 3, 4]); - expect(captured, "3 captured").toEqual([0, 1, 2, 3, 4, 5, 6, 3, 4]); - dispose(); - })); + }), + ] as const); - it("uses fallback if length is 0", () => - createRoot(dispose => { - const [length, setLength] = createSignal(4); - const captured: (string | number)[] = []; - const mapped = repeat( + expect(mapped(), "initial mapped").toEqual([0, 1, 2, 3, 4]); + expect(captured, "initial captured").toEqual([0, 1, 2, 3, 4]); + setLength(7); + flush(); + expect(mapped(), "1 mapped").toEqual([0, 1, 2, 3, 4, 5, 6]); + expect(captured, "1 captured").toEqual([0, 1, 2, 3, 4, 5, 6]); + setLength(3); + flush(); + expect(mapped(), "2 mapped").toEqual([0, 1, 2]); + expect(captured, "2 captured").toEqual([0, 1, 2, 3, 4, 5, 6]); + setLength(5); + flush(); + expect(mapped(), "3 mapped").toEqual([0, 1, 2, 3, 4]); + expect(captured, "3 captured").toEqual([0, 1, 2, 3, 4, 5, 6, 3, 4]); + dispose(); + }); + + it("uses fallback if length is 0", () => { + const [length, setLength] = createSignal(4); + const captured: (string | number)[] = []; + + const [dispose, mapped] = createRoot(dispose => [ + dispose, + repeat( length, i => { captured.push(i); @@ -41,23 +50,29 @@ describe("repeat", () => { return "fb"; }, }, - ); - expect(mapped(), "initial mapped").toEqual([0, 1, 2, 3]); - expect(captured, "initial captured").toEqual([0, 1, 2, 3]); - setLength(0); - expect(mapped(), "1 mapped").toEqual(["fb"]); - expect(captured, "1 captured").toEqual([0, 1, 2, 3, "fb"]); - setLength(3); - expect(mapped(), "2 mapped").toEqual([0, 1, 2]); - expect(captured, "2 captured").toEqual([0, 1, 2, 3, "fb", 0, 1, 2]); - dispose(); - })); + ), + ] as const); + + expect(mapped(), "initial mapped").toEqual([0, 1, 2, 3]); + expect(captured, "initial captured").toEqual([0, 1, 2, 3]); + setLength(0); + flush(); + expect(mapped(), "1 mapped").toEqual(["fb"]); + expect(captured, "1 captured").toEqual([0, 1, 2, 3, "fb"]); + setLength(3); + flush(); + expect(mapped(), "2 mapped").toEqual([0, 1, 2]); + expect(captured, "2 captured").toEqual([0, 1, 2, 3, "fb", 0, 1, 2]); + dispose(); + }); + + it("disposing on remove and cleanup", () => { + const [length, setLength] = createSignal(2); + const cleanups: (string | number)[] = []; - it("disposing on remove and cleanup", () => - createRoot(dispose => { - const [length, setLength] = createSignal(2); - const cleanups: (string | number)[] = []; - const mapped = repeat( + const [dispose, mapped] = createRoot(dispose => [ + dispose, + repeat( length, i => { onCleanup(() => cleanups.push(i)); @@ -69,19 +84,22 @@ describe("repeat", () => { return "fb"; }, }, - ); - // cleanups happen on access - createComputed(mapped); - expect(cleanups, "initial cleanups").toEqual([]); - setLength(1); - expect(cleanups, "1 cleanups").toEqual([1]); - setLength(0); - expect(cleanups, "2 cleanups").toEqual([1, 0]); - dispose(); - expect(cleanups, "3 cleanups").toEqual([1, 0, "fb"]); - setLength(3); - expect(mapped(), "mapped after dispose").toEqual(["fb"]); - })); + ), + ] as const); + + mapped(); + expect(cleanups, "initial cleanups").toEqual([]); + setLength(1); + flush(); + mapped(); + expect(cleanups, "1 cleanups").toEqual([1]); + setLength(0); + flush(); + mapped(); + expect(cleanups, "2 cleanups").toEqual([1, 0]); + dispose(); + expect(cleanups, "3 cleanups").toEqual([1, 0, "fb"]); + }); it("uses fallback when length is initially 0", () => createRoot(disposer => { @@ -99,7 +117,8 @@ describe("", () => { it("notifies observers on length change", () => { const [length, setLength] = createSignal(3); - const [dispose, accessor] = createRoot(dispose => { + let notifications = 0; + const dispose = createRoot(dispose => { const accessor = Repeat({ get times() { return length(); @@ -107,25 +126,33 @@ describe("", () => { fallback: () => 0, children: () => 1, }) as never as () => {}; - return [dispose, accessor]; - }); - let notifications = 0; - createComputed(() => { - accessor(); - notifications++; + createEffect( + () => accessor(), + () => { + notifications++; + }, + ); + + return dispose; }); + flush(); expect(notifications).toEqual(1); setLength(4); + flush(); expect(notifications).toEqual(2); setLength(0); + flush(); expect(notifications).toEqual(3); setLength(2); + flush(); expect(notifications).toEqual(4); setLength(1); + flush(); expect(notifications).toEqual(5); setLength(1.5); + flush(); expect(notifications).toEqual(5); dispose(); diff --git a/packages/range/test/server.test.ts b/packages/range/test/server.test.ts new file mode 100644 index 000000000..1c5218129 --- /dev/null +++ b/packages/range/test/server.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import { createRoot } from "solid-js"; +import { repeat, mapRange, indexRange } from "../src/index.js"; + +describe("API doesn't break in SSR", () => { + it("repeat() - SSR", () => + createRoot(dispose => { + const mapped = repeat( + () => 3, + i => i, + ); + expect(mapped()).toEqual([0, 1, 2]); + dispose(); + })); + + it("mapRange() - SSR", () => + createRoot(dispose => { + const mapped = mapRange( + () => 0, + () => 3, + () => 1, + n => n, + ); + expect(mapped()).toEqual([0, 1, 2]); + dispose(); + })); + + it("indexRange() - SSR", () => + createRoot(dispose => { + const mapped = indexRange( + () => 0, + () => 3, + () => 1, + n => n(), + ); + expect(mapped()).toEqual([0, 1, 2]); + dispose(); + })); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee1923c5f..e0210056d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -814,12 +814,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.13 + version: 2.0.0-beta.13(solid-js@2.0.0-beta.13) solid-js: - specifier: ^1.9.7 - version: 1.9.7 - solid-transition-group: - specifier: ^0.2.3 - version: 0.2.3(solid-js@1.9.7) + specifier: 2.0.0-beta.13 + version: 2.0.0-beta.13 packages/refs: dependencies: