ripple 0.3.126 → 0.3.127
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 +20 -0
- package/package.json +2 -2
- package/src/jsx-runtime.d.ts +1128 -967
- package/tests/utils/jsx-runtime-types.test.js +106 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} source
|
|
6
|
+
* @param {boolean} automatic
|
|
7
|
+
*/
|
|
8
|
+
function create_service(source, automatic) {
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const file_name = `${root}/packages/ripple/jsx-types-test.tsx`;
|
|
11
|
+
const options = {
|
|
12
|
+
strict: true,
|
|
13
|
+
target: ts.ScriptTarget.ESNext,
|
|
14
|
+
module: ts.ModuleKind.ESNext,
|
|
15
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
16
|
+
jsx: ts.JsxEmit.Preserve,
|
|
17
|
+
...(automatic ? { jsxImportSource: 'ripple' } : {}),
|
|
18
|
+
skipLibCheck: true,
|
|
19
|
+
types: [],
|
|
20
|
+
paths: {
|
|
21
|
+
'#public': [`${root}/packages/ripple/types/index.d.ts`],
|
|
22
|
+
ripple: [`${root}/packages/ripple/types/index.d.ts`],
|
|
23
|
+
'ripple/jsx-runtime': [`${root}/packages/ripple/src/jsx-runtime.d.ts`],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const service = ts.createLanguageService({
|
|
27
|
+
getCompilationSettings: () => options,
|
|
28
|
+
getScriptFileNames: () => [file_name],
|
|
29
|
+
getScriptVersion: () => '0',
|
|
30
|
+
getScriptSnapshot: (name) => {
|
|
31
|
+
const text = name === file_name ? source : ts.sys.readFile(name);
|
|
32
|
+
return text === undefined ? undefined : ts.ScriptSnapshot.fromString(text);
|
|
33
|
+
},
|
|
34
|
+
getCurrentDirectory: () => root,
|
|
35
|
+
getDefaultLibFileName: ts.getDefaultLibFilePath,
|
|
36
|
+
realpath: ts.sys.realpath,
|
|
37
|
+
directoryExists: ts.sys.directoryExists,
|
|
38
|
+
getDirectories: ts.sys.getDirectories,
|
|
39
|
+
fileExists: (name) => name === file_name || ts.sys.fileExists(name),
|
|
40
|
+
readFile: (name) => (name === file_name ? source : ts.sys.readFile(name)),
|
|
41
|
+
});
|
|
42
|
+
return { service, file_name };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('Ripple JSX types', () => {
|
|
46
|
+
it.each([true, false])('qualifies HTML tag hovers (automatic runtime: %s)', (automatic) => {
|
|
47
|
+
const source = `
|
|
48
|
+
import 'ripple/jsx-runtime';
|
|
49
|
+
const paragraph = <p />;
|
|
50
|
+
const input = <input />;
|
|
51
|
+
`;
|
|
52
|
+
const { service, file_name } = create_service(source, automatic);
|
|
53
|
+
try {
|
|
54
|
+
expect(service.getSemanticDiagnostics(file_name)).toEqual([]);
|
|
55
|
+
for (const [tag, attributes, element] of [
|
|
56
|
+
['p', 'HTMLAttributes', 'HTMLParagraphElement'],
|
|
57
|
+
['input', 'InputHTMLAttributes', 'HTMLInputElement'],
|
|
58
|
+
]) {
|
|
59
|
+
const info = service.getQuickInfoAtPosition(file_name, source.indexOf(`<${tag} `) + 1);
|
|
60
|
+
expect(ts.displayPartsToString(info?.displayParts)).toBe(
|
|
61
|
+
`(property) Ripple.JSX.IntrinsicElements.${tag}: Ripple.DetailedHTMLProps<Ripple.${attributes}<${element}>, ${element}>`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
service.dispose();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('preserves JSX imports, global types, native events, and DOM refs', () => {
|
|
70
|
+
const source = `
|
|
71
|
+
import type { ClassValue, JSX as RuntimeJSX, Ripple } from 'ripple/jsx-runtime';
|
|
72
|
+
import { createRefKey, type RefKey } from 'ripple';
|
|
73
|
+
|
|
74
|
+
const ref_key: RefKey = createRefKey();
|
|
75
|
+
const props: Ripple.DetailedHTMLProps<Ripple.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
|
|
76
|
+
value: 'hello',
|
|
77
|
+
ref: (node) => { node.select(); },
|
|
78
|
+
[ref_key]: (node) => { node.select(); },
|
|
79
|
+
onInput(event) { const input: HTMLInputElement = event.currentTarget; input.select(); },
|
|
80
|
+
onClick: { handleEvent(event) { event.currentTarget.select(); } },
|
|
81
|
+
};
|
|
82
|
+
const global_props: JSX.IntrinsicElements['input'] = props;
|
|
83
|
+
const runtime_props: RuntimeJSX.IntrinsicElements['input'] = global_props;
|
|
84
|
+
const classes: ClassValue = ['one', { two: true }];
|
|
85
|
+
const input = <input {...runtime_props} class={classes} />;
|
|
86
|
+
const svg = <svg><circle cx={10} ref={(node) => { const circle: SVGCircleElement = node; }} /></svg>;
|
|
87
|
+
// @ts-expect-error Input values do not accept objects.
|
|
88
|
+
const bad_value = <input value={{}} />;
|
|
89
|
+
// @ts-expect-error Refs remain specific to the element.
|
|
90
|
+
const bad_ref = <p ref={(node: HTMLInputElement) => { node.select(); }} />;
|
|
91
|
+
// @ts-expect-error Event currentTarget remains specific to the element.
|
|
92
|
+
const bad_event = <p onClick={(event) => { event.currentTarget.select(); }} />;
|
|
93
|
+
`;
|
|
94
|
+
const { service, file_name } = create_service(source, true);
|
|
95
|
+
try {
|
|
96
|
+
const diagnostics = service.getSemanticDiagnostics(file_name);
|
|
97
|
+
expect(
|
|
98
|
+
diagnostics.map((diagnostic) =>
|
|
99
|
+
ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
100
|
+
),
|
|
101
|
+
).toEqual([]);
|
|
102
|
+
} finally {
|
|
103
|
+
service.dispose();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|