Skip to content

Commit 4d5bcc8

Browse files
committed
Merge branch 'master' into feature/solid-results
2 parents 6f83eb8 + ed774cf commit 4d5bcc8

File tree

97 files changed

+1452
-1052
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1452
-1052
lines changed
Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,81 @@
11
import { createRoot } from "solid-js";
2-
import { describe, expect, it } from "vitest";
2+
import { describe, expect, it, vi } from "vitest";
33
import { createEvent } from "../../src/ts/hooks/createEvent";
44

55
describe("createEvent", () => {
6-
it("initial value is 0", () => {
7-
createRoot((dispose) => {
8-
const [event] = createEvent();
9-
expect(event()).toBe(0);
10-
dispose();
11-
});
6+
it("dispatch notifies subscribers", () => {
7+
const event = createEvent<string>();
8+
const fn = vi.fn();
9+
event.subscribe(fn);
10+
event.dispatch("hello");
11+
expect(fn).toHaveBeenCalledWith("hello");
1212
});
1313

14-
it("dispatch increments the value by 1", () => {
15-
createRoot((dispose) => {
16-
const [event, dispatch] = createEvent();
17-
dispatch();
18-
expect(event()).toBe(1);
19-
dispose();
20-
});
14+
it("dispatch notifies multiple subscribers", () => {
15+
const event = createEvent<number>();
16+
const fn1 = vi.fn();
17+
const fn2 = vi.fn();
18+
event.subscribe(fn1);
19+
event.subscribe(fn2);
20+
event.dispatch(42);
21+
expect(fn1).toHaveBeenCalledWith(42);
22+
expect(fn2).toHaveBeenCalledWith(42);
2123
});
2224

23-
it("each dispatch increments independently", () => {
24-
createRoot((dispose) => {
25-
const [event, dispatch] = createEvent();
26-
dispatch();
27-
dispatch();
28-
dispatch();
29-
expect(event()).toBe(3);
30-
dispose();
31-
});
25+
it("dispatch with no type arg requires no arguments", () => {
26+
const event = createEvent();
27+
const fn = vi.fn();
28+
event.subscribe(fn);
29+
event.dispatch();
30+
expect(fn).toHaveBeenCalledTimes(1);
31+
});
32+
33+
it("subscribe returns an unsubscribe function", () => {
34+
const event = createEvent<string>();
35+
const fn = vi.fn();
36+
const unsub = event.subscribe(fn);
37+
event.dispatch("a");
38+
unsub();
39+
event.dispatch("b");
40+
expect(fn).toHaveBeenCalledTimes(1);
41+
expect(fn).toHaveBeenCalledWith("a");
3242
});
3343

3444
it("two independent events do not share state", () => {
45+
const eventA = createEvent<string>();
46+
const eventB = createEvent<string>();
47+
const fnA = vi.fn();
48+
const fnB = vi.fn();
49+
eventA.subscribe(fnA);
50+
eventB.subscribe(fnB);
51+
eventA.dispatch("a");
52+
expect(fnA).toHaveBeenCalledWith("a");
53+
expect(fnB).not.toHaveBeenCalled();
54+
});
55+
56+
it("useListener auto-unsubscribes on dispose", () => {
57+
const event = createEvent<string>();
58+
const fn = vi.fn();
3559
createRoot((dispose) => {
36-
const [eventA, dispatchA] = createEvent();
37-
const [eventB, dispatchB] = createEvent();
38-
dispatchA();
39-
dispatchA();
40-
dispatchB();
41-
expect(eventA()).toBe(2);
42-
expect(eventB()).toBe(1);
60+
event.useListener(fn);
61+
event.dispatch("inside");
4362
dispose();
4463
});
64+
event.dispatch("outside");
65+
expect(fn).toHaveBeenCalledTimes(1);
66+
expect(fn).toHaveBeenCalledWith("inside");
67+
});
68+
69+
it("subscriber errors do not prevent other subscribers from running", () => {
70+
const event = createEvent<string>();
71+
const fn1 = vi.fn(() => {
72+
throw new Error("oops");
73+
});
74+
const fn2 = vi.fn();
75+
event.subscribe(fn1);
76+
event.subscribe(fn2);
77+
event.dispatch("test");
78+
expect(fn1).toHaveBeenCalled();
79+
expect(fn2).toHaveBeenCalled();
4580
});
4681
});

frontend/__tests__/root/config.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
Config as ConfigType,
1111
CaretStyleSchema,
1212
} from "@monkeytype/schemas/configs";
13-
import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation";
13+
import * as FunboxValidation from "../../src/ts/config/funbox-validation";
1414
import * as ConfigValidation from "../../src/ts/config/validation";
15-
import * as ConfigEvent from "../../src/ts/observables/config-event";
15+
import { configEvent } from "../../src/ts/events/config";
1616
import * as ApeConfig from "../../src/ts/ape/config";
1717
import * as Notifications from "../../src/ts/states/notifications";
1818
const { replaceConfig, getConfig } = __testing;
@@ -33,7 +33,7 @@ describe("Config", () => {
3333
ConfigValidation,
3434
"isConfigValueValid",
3535
);
36-
const dispatchConfigEventMock = vi.spyOn(ConfigEvent, "dispatch");
36+
const dispatchConfigEventMock = vi.spyOn(configEvent, "dispatch");
3737
const dbSaveConfigMock = vi.spyOn(ApeConfig, "saveConfig");
3838
const notificationAddMock = vi.spyOn(
3939
Notifications,

frontend/__tests__/test/funbox/funbox-validation.spec.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
1-
import { describe, it, expect, afterEach, vi } from "vitest";
2-
import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/test/funbox/funbox-validation";
1+
import { describe, it, expect } from "vitest";
2+
import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/config/funbox-validation";
33

4-
import * as Notifications from "../../../src/ts/states/notifications";
54
import { FunboxName } from "@monkeytype/schemas/configs";
65
describe("funbox-validation", () => {
76
describe("canSetConfigWithCurrentFunboxes", () => {
8-
const addNotificationMock = vi.spyOn(
9-
Notifications,
10-
"showNoticeNotification",
11-
);
12-
afterEach(() => {
13-
addNotificationMock.mockClear();
14-
});
15-
167
const testCases = [
178
//checks for frontendForcedConfig
189
{
1910
key: "mode",
2011
value: "zen",
2112
funbox: ["memory"],
22-
error: "You can't set mode to zen with currently active funboxes.",
13+
expected: false,
2314
},
24-
{ key: "mode", value: "words", funbox: ["memory"] }, //ok
15+
{ key: "mode", value: "words", funbox: ["memory"], expected: true },
2516

2617
//checks for zen mode
2718
...[
@@ -40,10 +31,15 @@ describe("funbox-validation", () => {
4031
key: "mode",
4132
value: "zen",
4233
funbox: [funbox],
43-
error: "You can't set mode to zen with currently active funboxes.",
34+
expected: false,
4435
})),
45-
{ key: "mode", value: "zen", funbox: ["mirror"] }, //ok
46-
{ key: "mode", value: "zen", funbox: ["space_balls"] }, //no frontendFunctions
36+
{ key: "mode", value: "zen", funbox: ["mirror"], expected: true },
37+
{
38+
key: "mode",
39+
value: "zen",
40+
funbox: ["space_balls"],
41+
expected: true,
42+
},
4743

4844
//checks for words and custom
4945
...["quote", "custom"].flatMap((value) =>
@@ -56,23 +52,22 @@ describe("funbox-validation", () => {
5652
key: "mode",
5753
value,
5854
funbox: [funbox],
59-
error: `You can't set mode to ${value} with currently active funboxes.`,
55+
expected: false,
6056
})),
6157
),
62-
{ key: "mode", value: "quote", funbox: ["space_balls"] }, //no frontendFunctions
58+
{
59+
key: "mode",
60+
value: "quote",
61+
funbox: ["space_balls"],
62+
expected: true,
63+
},
6364
];
6465
it.for(testCases)(
6566
`check $funbox with $key=$value`,
66-
({ key, value, funbox, error }) => {
67+
({ key, value, funbox, expected }) => {
6768
expect(
6869
canSetConfigWithCurrentFunboxes(key, value, funbox as FunboxName[]),
69-
).toBe(error === undefined);
70-
71-
if (error !== undefined) {
72-
expect(addNotificationMock).toHaveBeenCalledWith(error, {
73-
durationMs: 5000,
74-
});
75-
}
70+
).toBe(expected);
7671
},
7772
);
7873
});

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"tsx": "4.21.0",
116116
"typescript": "6.0.0-beta",
117117
"unplugin-inject-preload": "3.0.0",
118-
"vite": "7.3.1",
118+
"vite": "8.0.1",
119119
"vite-bundle-visualizer": "1.2.1",
120120
"vite-plugin-html-inject": "1.1.2",
121121
"vite-plugin-inspect": "11.3.3",

frontend/scripts/import-tree.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from "node:fs";
22
import path from "node:path";
3+
import ts from "typescript";
34

45
const ROOT = path.resolve(import.meta.dirname, "..");
56

@@ -34,8 +35,11 @@ function collectTsFiles(dir: string): string[] {
3435
return results;
3536
}
3637

38+
const isDir = fs.statSync(resolved).isDirectory();
39+
const boundary = isDir ? resolved : null;
40+
3741
let entryPoints: string[];
38-
if (fs.statSync(resolved).isDirectory()) {
42+
if (isDir) {
3943
entryPoints = collectTsFiles(resolved);
4044
} else {
4145
entryPoints = [resolved];
@@ -46,15 +50,35 @@ if (entryPoints.length === 0) {
4650
process.exit(1);
4751
}
4852

49-
// --- Import extraction ---
53+
// --- Import extraction (type-aware) ---
54+
55+
const tsConfig: ts.CompilerOptions = {
56+
module: ts.ModuleKind.ESNext,
57+
target: ts.ScriptTarget.ESNext,
58+
jsx: ts.JsxEmit.Preserve,
59+
sourceMap: false,
60+
declaration: false,
61+
isolatedModules: true,
62+
};
5063

51-
const IMPORT_RE =
52-
/(?:import|export)\s+(?:type\s+)?(?:(?:\{[^}]*\}|[\w*]+(?:\s*,\s*\{[^}]*\})?)\s+from\s+)?["']([^"']+)["']/g;
64+
const JS_IMPORT_RE =
65+
/(?:import|export)\s+(?:(?:\{[^}]*\}|[\w*]+(?:\s*,\s*\{[^}]*\})?)\s+from\s+)?["']([^"']+)["']/g;
5366

5467
function extractImports(filePath: string): string[] {
5568
const content = fs.readFileSync(filePath, "utf-8");
69+
let outputText: string;
70+
try {
71+
({ outputText } = ts.transpileModule(content, {
72+
compilerOptions: tsConfig,
73+
fileName: filePath,
74+
}));
75+
} catch {
76+
// Some files (e.g. declaration files) can't be transpiled — fall back to
77+
// regex on the original source, which still strips type-only imports.
78+
outputText = content;
79+
}
5680
const specifiers: string[] = [];
57-
for (const match of content.matchAll(IMPORT_RE)) {
81+
for (const match of outputText.matchAll(JS_IMPORT_RE)) {
5882
const spec = match[1];
5983
if (spec !== undefined) specifiers.push(spec);
6084
}
@@ -190,6 +214,12 @@ function depthColor(depth: number): string {
190214

191215
// --- Display ---
192216

217+
function leavesFolder(filePath: string): boolean {
218+
if (boundary === null) return false;
219+
if (filePath.startsWith("@monkeytype/")) return true;
220+
return !filePath.startsWith(boundary + "/");
221+
}
222+
193223
function displayPath(filePath: string): string {
194224
if (filePath.startsWith(ROOT + "/")) {
195225
return path.relative(ROOT, filePath);
@@ -210,9 +240,12 @@ function printTree(
210240
const connector = isRoot ? "" : isLast ? "└── " : "├── ";
211241
const dc = depthColor(depth);
212242

243+
const leaves = !isRoot && leavesFolder(filePath);
244+
const leavesTag = leaves ? ` ${c.red}[↑]${c.reset}` : "";
245+
213246
if (!info) {
214247
// leaf node (e.g. @monkeytype package)
215-
console.log(`${c.dim}${prefix}${connector}${dp}${c.reset}`);
248+
console.log(`${c.dim}${prefix}${connector}${dp}${c.reset}${leavesTag}`);
216249
return;
217250
}
218251

@@ -225,7 +258,7 @@ function printTree(
225258
const seen = !isRoot && printed.has(filePath);
226259
const seenTag = seen ? ` ${c.dim}[seen above]${c.reset}` : "";
227260
console.log(
228-
`${c.dim}${prefix}${connector}${c.reset}${nameStyle}${dp}${c.reset}${stats}${seenTag}`,
261+
`${c.dim}${prefix}${connector}${c.reset}${nameStyle}${dp}${c.reset}${stats}${leavesTag}${seenTag}`,
229262
);
230263

231264
if (seen || depth >= maxDepthLimit) return;
@@ -336,3 +369,21 @@ console.log(
336369
console.log(
337370
`Max depth: ${c.bold}${maxDepthSeen}${c.reset} ${c.dim}(${displayPath(maxDepthFile)})${c.reset}`,
338371
);
372+
373+
if (boundary !== null) {
374+
const externalDirect = new Set<string>();
375+
const externalTransitive = new Set<string>();
376+
for (const entry of entryPoints) {
377+
const info = cache.get(entry);
378+
if (!info) continue;
379+
for (const dep of info.directImports) {
380+
if (leavesFolder(dep)) externalDirect.add(dep);
381+
}
382+
for (const dep of getAllReachable(entry, new Set())) {
383+
if (leavesFolder(dep)) externalTransitive.add(dep);
384+
}
385+
}
386+
console.log(
387+
`Leaves folder ${c.red}[↑]${c.reset}: ${c.bold}${externalDirect.size}${c.reset} direct, ${c.bold}${externalTransitive.size}${c.reset} transitive`,
388+
);
389+
}

frontend/src/terms-of-service.html

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -211,26 +211,28 @@ <h1>Limitations</h1>
211211
Users as a whole.
212212
</p>
213213
<h1>Privacy Policy</h1>
214-
If you use our Services, you must abide by our Privacy Policy. You
215-
acknowledge that you have read our
216-
<a
217-
href="https://monkeytype.com/privacy-policy"
218-
target="_blank"
219-
rel="noreferrer noopener"
220-
>
221-
Privacy Policy
222-
</a>
223-
&nbsp;and understand that it sets forth how we collect, use, and store
224-
your information. If you do not agree with our Privacy Statement, then
225-
you must stop using the Services immediately. Any person, entity, or
226-
service collecting data from the Services must comply with our Privacy
227-
Statement. Misuse of any User's Personal Information is prohibited. If
228-
you collect any Personal Information from a User, you agree that you
229-
will only use the Personal Information you gather for the purpose for
230-
which the User has authorized it. You agree that you will reasonably
231-
secure any Personal Information you have gathered from the Services, and
232-
you will respond promptly to complaints, removal requests, and 'do not
233-
contact' requests from us or Users.
214+
<div>
215+
If you use our Services, you must abide by our Privacy Policy. You
216+
acknowledge that you have read our
217+
<a
218+
href="https://monkeytype.com/privacy-policy"
219+
target="_blank"
220+
rel="noreferrer noopener"
221+
>
222+
Privacy Policy
223+
</a>
224+
&nbsp;and understand that it sets forth how we collect, use, and store
225+
your information. If you do not agree with our Privacy Statement, then
226+
you must stop using the Services immediately. Any person, entity, or
227+
service collecting data from the Services must comply with our Privacy
228+
Statement. Misuse of any User's Personal Information is prohibited. If
229+
you collect any Personal Information from a User, you agree that you
230+
will only use the Personal Information you gather for the purpose for
231+
which the User has authorized it. You agree that you will reasonably
232+
secure any Personal Information you have gathered from the Services,
233+
and you will respond promptly to complaints, removal requests, and 'do
234+
not contact' requests from us or Users.
235+
</div>
234236

235237
<h1>Limitations on Automated Use</h1>
236238
You shouldn't use bots or access our Services in malicious or prohibited

0 commit comments

Comments
 (0)