@go-go-scope/adapter-svelte 2.7.0

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.
@@ -0,0 +1,189 @@
1
+ <script lang="ts">
2
+ import {
3
+ createScope,
4
+ createTask,
5
+ createParallel,
6
+ createChannel,
7
+ createBroadcast,
8
+ createPolling
9
+ } from "../src/index.js";
10
+
11
+ export let testType: string;
12
+ export let onDispose: () => void = () => {};
13
+ export let onBroadcast: (msg: string) => void = () => {};
14
+
15
+ // Create all possible stores at top level
16
+ const scope = createScope({ name: "test" });
17
+ scope.onDispose(onDispose);
18
+
19
+ const task = createTask(async () => "fetched-data", { immediate: true });
20
+ const manualTask = createTask(async () => "result-1", { immediate: false });
21
+ const errorTask = createTask(async () => { throw new Error("Task failed"); }, { immediate: false });
22
+
23
+ const delays = [50, 30, 70];
24
+ const factories = delays.map((delay, i) => async () => {
25
+ await new Promise(r => setTimeout(r, delay));
26
+ return `task-${i}`;
27
+ });
28
+ const parallel = createParallel(factories, { immediate: true });
29
+
30
+ const ch = createChannel<string>();
31
+ const chClose = createChannel<string>();
32
+
33
+ const bus = createBroadcast<string>();
34
+ bus.subscribe((msg) => onBroadcast(msg));
35
+
36
+ let pollCount1 = 0;
37
+ const poller = createPolling(async () => {
38
+ pollCount1++;
39
+ return `poll-${pollCount1}`;
40
+ }, { interval: 1000, immediate: true });
41
+
42
+ let pollCount2 = 0;
43
+ const pollerStop = createPolling(async () => {
44
+ pollCount2++;
45
+ return `poll-${pollCount2}`;
46
+ }, { interval: 1000, immediate: true });
47
+
48
+ // Subscribe to store values individually
49
+ let scopeActiveVal = false;
50
+ scope.isActive.subscribe(v => scopeActiveVal = v);
51
+ $: scopeActive = String(scopeActiveVal);
52
+
53
+ let taskDataVal: string | undefined;
54
+ task.data.subscribe(v => taskDataVal = v);
55
+ $: taskData = taskDataVal ?? "no-data";
56
+
57
+ let taskLoadingVal = false;
58
+ task.isLoading.subscribe(v => taskLoadingVal = v);
59
+ $: taskLoading = String(taskLoadingVal);
60
+
61
+ let manualTaskDataVal: string | undefined;
62
+ manualTask.data.subscribe(v => manualTaskDataVal = v);
63
+ $: manualTaskData = manualTaskDataVal ?? "no-data";
64
+
65
+ let errorTaskErrorVal: Error | undefined;
66
+ errorTask.error.subscribe(v => errorTaskErrorVal = v);
67
+ $: errorTaskError = errorTaskErrorVal?.message ?? "no-error";
68
+
69
+ let errorTaskDataVal: string | undefined;
70
+ errorTask.data.subscribe(v => errorTaskDataVal = v);
71
+ $: errorTaskData = errorTaskDataVal ?? "no-data";
72
+
73
+ let parallelProgressVal = 0;
74
+ parallel.progress.subscribe(v => parallelProgressVal = v);
75
+ $: parallelProgress = String(parallelProgressVal);
76
+
77
+ let parallelResultsVal: (string | undefined)[] = [];
78
+ parallel.results.subscribe(v => {
79
+ parallelResultsVal = v;
80
+ });
81
+ $: parallelResults = parallelResultsVal ? parallelResultsVal.filter((r): r is string => r !== undefined) : [];
82
+
83
+ let channelLatestVal: string | undefined;
84
+ ch.latest.subscribe(v => channelLatestVal = v);
85
+ $: channelLatest = channelLatestVal ?? "no-msg";
86
+
87
+ let channelHistoryVal: string[] = [];
88
+ ch.history.subscribe(v => channelHistoryVal = v);
89
+ $: channelHistory = [...channelHistoryVal];
90
+
91
+ let channelClosedVal = false;
92
+ chClose.isClosed.subscribe(v => channelClosedVal = v);
93
+ $: channelClosed = String(channelClosedVal);
94
+
95
+ let broadcastLatestVal: string | undefined;
96
+ bus.latest.subscribe(v => broadcastLatestVal = v);
97
+ $: broadcastLatest = broadcastLatestVal ?? "no-msg";
98
+
99
+ let pollDataVal: string | undefined;
100
+ poller.data.subscribe(v => pollDataVal = v);
101
+ $: pollData = pollDataVal ?? "no-data";
102
+
103
+ let pollCountVal = 0;
104
+ poller.pollCount.subscribe(v => pollCountVal = v);
105
+ $: pollCountStr = String(pollCountVal);
106
+
107
+ let isPollingVal = false;
108
+ pollerStop.isPolling.subscribe(v => isPollingVal = v);
109
+ $: isPolling = String(isPollingVal);
110
+
111
+ let pollStopDataVal: string | undefined;
112
+ pollerStop.data.subscribe(v => pollStopDataVal = v);
113
+ $: pollStopData = pollStopDataVal ?? "no-data";
114
+
115
+ // Action handlers
116
+ async function executeManualTask() {
117
+ await manualTask.execute();
118
+ }
119
+
120
+ async function executeErrorTask() {
121
+ await errorTask.execute();
122
+ }
123
+
124
+ async function sendMessages() {
125
+ await ch.send("msg-1");
126
+ await ch.send("msg-2");
127
+ }
128
+
129
+ function doBroadcast() {
130
+ bus.broadcast("hello");
131
+ bus.broadcast("world");
132
+ }
133
+
134
+ function stopPolling() {
135
+ pollerStop.stop();
136
+ }
137
+ </script>
138
+
139
+ {#if testType === "scope"}
140
+ <span data-testid="active">{scopeActive}</span>
141
+ {/if}
142
+
143
+ {#if testType === "task-immediate"}
144
+ <span data-testid="data">{taskData}</span>
145
+ <span data-testid="loading">{taskLoading}</span>
146
+ {/if}
147
+
148
+ {#if testType === "task-manual"}
149
+ <span data-testid="data">{manualTaskData}</span>
150
+ <button data-testid="execute-btn" on:click={executeManualTask}>Execute</button>
151
+ {/if}
152
+
153
+ {#if testType === "task-error"}
154
+ <span data-testid="data">{errorTaskData}</span>
155
+ <span data-testid="error">{errorTaskError}</span>
156
+ <button data-testid="execute-btn" on:click={executeErrorTask}>Execute</button>
157
+ {/if}
158
+
159
+ {#if testType === "parallel"}
160
+ <span data-testid="progress">{parallelProgress}</span>
161
+ <span data-testid="results">{JSON.stringify(parallelResults)}</span>
162
+ {/if}
163
+
164
+ {#if testType === "channel"}
165
+ <span data-testid="latest">{channelLatest}</span>
166
+ <span data-testid="history">{JSON.stringify(channelHistory)}</span>
167
+ <button data-testid="send-btn" on:click={sendMessages}>Send</button>
168
+ {/if}
169
+
170
+ {#if testType === "channel-close"}
171
+ <span data-testid="closed">{channelClosed}</span>
172
+ <button data-testid="close-btn" on:click={() => chClose.close()}>Close</button>
173
+ {/if}
174
+
175
+ {#if testType === "broadcast"}
176
+ <span data-testid="latest">{broadcastLatest}</span>
177
+ <button data-testid="broadcast-btn" on:click={doBroadcast}>Broadcast</button>
178
+ {/if}
179
+
180
+ {#if testType === "polling"}
181
+ <span data-testid="data">{pollData}</span>
182
+ <span data-testid="count">{pollCountStr}</span>
183
+ {/if}
184
+
185
+ {#if testType === "polling-stop"}
186
+ <span data-testid="data">{pollStopData}</span>
187
+ <span data-testid="polling">{isPolling}</span>
188
+ <button data-testid="stop-btn" on:click={stopPolling}>Stop</button>
189
+ {/if}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Integration tests for @go-go-scope/adapter-svelte
3
+ */
4
+ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
5
+ import { render, screen, waitFor, fireEvent } from "@testing-library/svelte";
6
+ import { tick } from "svelte";
7
+ import Component from "./TestComponent.svelte";
8
+
9
+ describe("Svelte adapter integration", () => {
10
+ describe("createScope", () => {
11
+ test("creates and auto-disposes scope", async () => {
12
+ const onDispose = vi.fn();
13
+
14
+ const { unmount } = render(Component, {
15
+ props: { testType: "scope", onDispose },
16
+ });
17
+
18
+ // Component should be active
19
+ expect(screen.getByTestId("active").textContent).toBe("true");
20
+
21
+ // Unmount
22
+ unmount();
23
+ await tick();
24
+
25
+ // Give time for async disposal
26
+ await new Promise((r) => setTimeout(r, 50));
27
+ expect(onDispose).toHaveBeenCalled();
28
+ });
29
+ });
30
+
31
+ describe("createTask", () => {
32
+ test("executes task immediately when immediate is true", async () => {
33
+ render(Component, {
34
+ props: { testType: "task-immediate" },
35
+ });
36
+
37
+ // Should start loading
38
+ expect(screen.getByTestId("loading").textContent).toBe("true");
39
+
40
+ // Wait for completion
41
+ await waitFor(() => {
42
+ expect(screen.getByTestId("data").textContent).toBe("fetched-data");
43
+ }, { timeout: 1000 });
44
+
45
+ expect(screen.getByTestId("loading").textContent).toBe("false");
46
+ });
47
+
48
+ test("manual execution with execute()", async () => {
49
+ const { component } = render(Component, {
50
+ props: { testType: "task-manual" },
51
+ });
52
+
53
+ // Should not have data initially
54
+ expect(screen.getByTestId("data").textContent).toBe("no-data");
55
+
56
+ // Execute manually
57
+ await fireEvent.click(screen.getByTestId("execute-btn"));
58
+
59
+ await waitFor(() => {
60
+ expect(screen.getByTestId("data").textContent).toBe("result-1");
61
+ });
62
+ });
63
+
64
+ test("handles task errors", async () => {
65
+ render(Component, {
66
+ props: { testType: "task-error" },
67
+ });
68
+
69
+ // Execute failing task
70
+ await fireEvent.click(screen.getByTestId("execute-btn"));
71
+
72
+ await waitFor(() => {
73
+ expect(screen.getByTestId("error").textContent).toBe("Task failed");
74
+ });
75
+
76
+ expect(screen.getByTestId("data").textContent).toBe("no-data");
77
+ });
78
+ });
79
+
80
+ describe("createParallel", () => {
81
+ test("executes tasks in parallel with progress", async () => {
82
+ render(Component, {
83
+ props: { testType: "parallel" },
84
+ });
85
+
86
+ // Should show progress
87
+ await waitFor(() => {
88
+ const progress = screen.getByTestId("progress").textContent;
89
+ return progress === "100";
90
+ }, { timeout: 5000 });
91
+
92
+ // Wait a bit more for results to update
93
+ await new Promise(r => setTimeout(r, 100));
94
+
95
+ const results = JSON.parse(screen.getByTestId("results").textContent ?? "[]");
96
+ expect(results).toContain("task-0");
97
+ expect(results).toContain("task-1");
98
+ expect(results).toContain("task-2");
99
+ });
100
+ });
101
+
102
+ describe("createChannel", () => {
103
+ test("sends and receives messages", async () => {
104
+ render(Component, {
105
+ props: { testType: "channel" },
106
+ });
107
+
108
+ // Send messages
109
+ await fireEvent.click(screen.getByTestId("send-btn"));
110
+
111
+ await waitFor(() => {
112
+ expect(screen.getByTestId("latest").textContent).toBe("msg-2");
113
+ });
114
+
115
+ const history = JSON.parse(screen.getByTestId("history").textContent ?? "[]");
116
+ expect(history).toEqual(["msg-1", "msg-2"]);
117
+ });
118
+
119
+ test("closes channel properly", async () => {
120
+ render(Component, {
121
+ props: { testType: "channel-close" },
122
+ });
123
+
124
+ expect(screen.getByTestId("closed").textContent).toBe("false");
125
+
126
+ // Close channel
127
+ await fireEvent.click(screen.getByTestId("close-btn"));
128
+
129
+ await waitFor(() => {
130
+ expect(screen.getByTestId("closed").textContent).toBe("true");
131
+ });
132
+ });
133
+ });
134
+
135
+ describe("createBroadcast", () => {
136
+ test("broadcasts to subscribers", async () => {
137
+ render(Component, {
138
+ props: {
139
+ testType: "broadcast",
140
+ },
141
+ });
142
+
143
+ // Initial state
144
+ expect(screen.getByTestId("latest").textContent).toBe("no-msg");
145
+
146
+ // Broadcast
147
+ await fireEvent.click(screen.getByTestId("broadcast-btn"));
148
+
149
+ await waitFor(() => {
150
+ expect(screen.getByTestId("latest").textContent).toBe("world");
151
+ });
152
+ });
153
+ });
154
+
155
+ describe("createPolling", () => {
156
+ beforeEach(() => {
157
+ vi.useFakeTimers();
158
+ });
159
+
160
+ afterEach(() => {
161
+ vi.useRealTimers();
162
+ });
163
+
164
+ test("polls at specified interval", async () => {
165
+ render(Component, {
166
+ props: { testType: "polling" },
167
+ });
168
+
169
+ // First poll
170
+ await vi.advanceTimersByTimeAsync(10);
171
+ await tick();
172
+
173
+ expect(screen.getByTestId("data").textContent).toBe("poll-1");
174
+
175
+ // Second poll
176
+ await vi.advanceTimersByTimeAsync(1000);
177
+ await tick();
178
+
179
+ expect(screen.getByTestId("data").textContent).toBe("poll-2");
180
+ });
181
+
182
+ test("stops polling when stop() called", async () => {
183
+ render(Component, {
184
+ props: { testType: "polling-stop" },
185
+ });
186
+
187
+ await vi.advanceTimersByTimeAsync(10);
188
+ await tick();
189
+
190
+ expect(screen.getByTestId("polling").textContent).toBe("true");
191
+
192
+ // Stop polling
193
+ await fireEvent.click(screen.getByTestId("stop-btn"));
194
+ await tick();
195
+
196
+ expect(screen.getByTestId("polling").textContent).toBe("false");
197
+ });
198
+ });
199
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "lib": ["ES2022", "ES2022.Error", "ESNext.Disposable", "DOM"],
7
+ "verbatimModuleSyntax": true
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist"]
11
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import { svelte } from "@sveltejs/vite-plugin-svelte";
3
+
4
+ export default defineConfig({
5
+ plugins: [svelte()],
6
+ test: {
7
+ environment: "jsdom",
8
+ globals: true,
9
+ },
10
+ resolve: {
11
+ conditions: ['browser'],
12
+ },
13
+ });