@es-joy/jsoe 0.27.0 → 0.28.1
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/CHANGES.md +37 -0
- package/README.md +3 -1
- package/badges/coverage-badge.svg +1 -1
- package/badges/tests-badge.svg +1 -0
- package/dist/formats/schema.d.ts +11 -0
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/formats/structuredCloning.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/fundamentalTypes/stringType.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/typeChoices.d.ts +32 -2
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/rawTypesonEditor.d.ts +110 -0
- package/dist/utils/rawTypesonEditor.d.ts.map +1 -0
- package/dist/vendor-imports.d.ts +4 -0
- package/docs/proposals/raw-typeson-edit-view.md +575 -0
- package/mmr.json +6 -0
- package/package.json +34 -12
- package/pnpm-workspace.yaml +32 -30
- package/src/formats/schema.js +10 -15
- package/src/formats/structuredCloning.js +24 -17
- package/src/fundamentalTypes/arrayType.js +119 -23
- package/src/fundamentalTypes/blobType.js +16 -16
- package/src/fundamentalTypes/fileType.js +13 -13
- package/src/fundamentalTypes/regexpType.js +1 -1
- package/src/fundamentalTypes/stringType.js +5 -0
- package/src/jsoe.css +12 -0
- package/src/subTypes/blobHTMLType.js +1 -1
- package/src/superTypes/dommatrixType.js +1 -1
- package/src/superTypes/errorsSpecialType.js +1 -1
- package/src/typeChoices.js +37 -10
- package/src/types.js +13 -8
- package/src/utils/media.js +3 -3
- package/src/utils/rawTypesonEditor.js +669 -0
- package/src/vendor-imports.js +12 -0
- package/tsconfig.json +1 -1
- package/typings/json-6.d.ts +12 -0
- package/vendor/@codemirror/autocomplete/dist/index.js +2125 -0
- package/vendor/@codemirror/commands/dist/index.js +1826 -0
- package/vendor/@codemirror/lang-javascript/dist/index.js +513 -0
- package/vendor/@codemirror/language/dist/index.js +2693 -0
- package/vendor/@codemirror/lint/dist/index.js +956 -0
- package/vendor/@codemirror/search/dist/index.js +1238 -0
- package/vendor/@codemirror/state/dist/index.js +3947 -0
- package/vendor/@codemirror/view/dist/index.js +11867 -0
- package/vendor/@lezer/common/dist/index.js +2202 -0
- package/vendor/@lezer/highlight/dist/index.js +927 -0
- package/vendor/@lezer/javascript/dist/index.js +192 -0
- package/vendor/@lezer/lr/dist/index.js +1889 -0
- package/vendor/@marijn/find-cluster-break/src/index.js +87 -0
- package/vendor/codemirror/dist/index.js +96 -0
- package/vendor/crelt/index.js +28 -0
- package/vendor/jamilih/dist/jml.mjs +898 -25
- package/vendor/json-6/dist/index.mjs +1783 -0
- package/vendor/style-mod/src/style-mod.js +172 -0
- package/vendor/w3c-keyname/index.js +119 -0
- package/vendor/zod/classic/checks.d.ts +1 -1
- package/vendor/zod/classic/checks.js +1 -1
- package/vendor/zod/classic/schemas.d.ts +2 -6
- package/vendor/zod/classic/schemas.js +9 -13
- package/vendor/zod/core/api.d.ts +8 -4
- package/vendor/zod/core/api.js +11 -23
- package/vendor/zod/core/checks.d.ts +16 -0
- package/vendor/zod/core/checks.js +31 -0
- package/vendor/zod/core/compile.js +20 -24
- package/vendor/zod/core/core.js +1 -1
- package/vendor/zod/core/json-schema-processors.d.ts +0 -1
- package/vendor/zod/core/json-schema-processors.js +8 -43
- package/vendor/zod/core/memoizer.js +2 -5
- package/vendor/zod/core/regexes.d.ts +1 -0
- package/vendor/zod/core/regexes.js +2 -0
- package/vendor/zod/core/schemas.d.ts +8 -23
- package/vendor/zod/core/schemas.js +39 -80
- package/vendor/zod/core/to-json-schema.d.ts +0 -3
- package/vendor/zod/core/to-json-schema.js +1 -1
- package/vendor/zod/core/util.js +0 -4
- package/vendor/zod/core/versions.js +1 -1
- package/vendor/zod/core/visit.js +0 -12
- package/vendor/zod/locales/ar.js +1 -0
- package/vendor/zod/locales/az.js +1 -0
- package/vendor/zod/locales/be.js +1 -0
- package/vendor/zod/locales/bg.js +1 -0
- package/vendor/zod/locales/bn.js +1 -0
- package/vendor/zod/locales/ca.js +1 -0
- package/vendor/zod/locales/ckb.js +1 -0
- package/vendor/zod/locales/cs.js +1 -0
- package/vendor/zod/locales/da.js +1 -0
- package/vendor/zod/locales/de.js +1 -0
- package/vendor/zod/locales/el.js +1 -0
- package/vendor/zod/locales/en.js +1 -0
- package/vendor/zod/locales/eo.js +1 -0
- package/vendor/zod/locales/es.js +1 -0
- package/vendor/zod/locales/fa.js +1 -0
- package/vendor/zod/locales/fi.js +1 -0
- package/vendor/zod/locales/fr-CA.js +1 -0
- package/vendor/zod/locales/fr.js +1 -0
- package/vendor/zod/locales/gu.js +1 -0
- package/vendor/zod/locales/he.js +1 -0
- package/vendor/zod/locales/hi.js +1 -0
- package/vendor/zod/locales/hr.js +1 -0
- package/vendor/zod/locales/hu.js +1 -0
- package/vendor/zod/locales/hy.js +1 -0
- package/vendor/zod/locales/id.js +1 -0
- package/vendor/zod/locales/is.js +1 -0
- package/vendor/zod/locales/it.js +1 -0
- package/vendor/zod/locales/ja.js +1 -0
- package/vendor/zod/locales/ka.js +1 -0
- package/vendor/zod/locales/km.js +1 -0
- package/vendor/zod/locales/kn.js +1 -0
- package/vendor/zod/locales/ko.js +1 -0
- package/vendor/zod/locales/lt.js +1 -0
- package/vendor/zod/locales/mk.js +1 -0
- package/vendor/zod/locales/ms.js +1 -0
- package/vendor/zod/locales/ne.js +1 -0
- package/vendor/zod/locales/nl.js +1 -0
- package/vendor/zod/locales/nn.js +1 -0
- package/vendor/zod/locales/no.js +1 -0
- package/vendor/zod/locales/ota.js +1 -0
- package/vendor/zod/locales/pl.js +1 -0
- package/vendor/zod/locales/ps.js +1 -0
- package/vendor/zod/locales/pt-BR.js +1 -0
- package/vendor/zod/locales/pt.js +1 -0
- package/vendor/zod/locales/ro.js +1 -0
- package/vendor/zod/locales/ru.js +1 -0
- package/vendor/zod/locales/sk.js +1 -0
- package/vendor/zod/locales/sl.js +1 -0
- package/vendor/zod/locales/sv.js +1 -0
- package/vendor/zod/locales/ta.js +1 -0
- package/vendor/zod/locales/tg.js +1 -0
- package/vendor/zod/locales/th.js +1 -0
- package/vendor/zod/locales/tk.js +1 -0
- package/vendor/zod/locales/tr.js +1 -0
- package/vendor/zod/locales/uk.js +1 -0
- package/vendor/zod/locales/ur.js +1 -0
- package/vendor/zod/locales/uz.js +1 -0
- package/vendor/zod/locales/vi.js +1 -0
- package/vendor/zod/locales/yo.js +1 -0
- package/vendor/zod/locales/zh-CN.js +1 -0
- package/vendor/zod/locales/zh-TW.js +1 -0
- package/vendor/zod/mini/checks.d.ts +1 -1
- package/vendor/zod/mini/checks.js +1 -1
- package/vendor/zod/mini/schemas.d.ts +2 -5
- package/vendor/zod/mini/schemas.js +9 -11
- package/vendor/zodexy/dist/esm/index.js +4 -33
- package/vendor/zodexy/dist/schema.zodexy.json +1 -17
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# Proposal: raw Typeson/JSON6 edit & view buttons on object/array controls
|
|
2
|
+
|
|
3
|
+
Status: implemented (see "Implementation notes" at the end for corrections
|
|
4
|
+
found while building it)
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
jsoe's README already carries an aspirational to-do: "Ability to replace
|
|
9
|
+
content with raw JSON6/Typeson/Safe Eval ... if it validates"
|
|
10
|
+
(`README.md:124`). This proposal implements it for every object- and
|
|
11
|
+
array-based control: a small "Edit raw" button in edit mode that opens a
|
|
12
|
+
syntax-highlighted textual view of the control's current value
|
|
13
|
+
(Typeson-encoded, written in JSON6 syntax) which can be hand-edited and saved
|
|
14
|
+
back in; and, only when a new opt-in "unsafe eval" privilege is granted, a
|
|
15
|
+
second mode in that same editor that instead evaluates the text as a literal
|
|
16
|
+
JS expression (mirroring the existing `eval(o)` pattern already used for
|
|
17
|
+
`arbitraryJS` functions in `src/formats/structuredCloning.js:23-27`). A
|
|
18
|
+
parallel read-only "View raw" button is added to view-mode (`viewUI`)
|
|
19
|
+
controls, showing the same highlighted Typeson/JSON6 text with no edit/eval
|
|
20
|
+
capability.
|
|
21
|
+
|
|
22
|
+
Because `objectType.js` and `arrayType.js` share one implementation
|
|
23
|
+
(`src/fundamentalTypes/arrayType.js`), and the root control renders through
|
|
24
|
+
that same code path (the `topRoot` pattern), adding the buttons there
|
|
25
|
+
automatically covers every object/array control in the tree, including root,
|
|
26
|
+
with no special-casing needed.
|
|
27
|
+
|
|
28
|
+
## 1. Scope: which controls get the buttons
|
|
29
|
+
|
|
30
|
+
The button applies to `type === 'object'` and `type === 'array'`, including
|
|
31
|
+
their schema-refined forms (`record`/`tuple`, detected the same way the file
|
|
32
|
+
already does via `recordMode`/`tupleMode`) and to `set`/`map` (also real
|
|
33
|
+
object/array data, fully representable in Typeson).
|
|
34
|
+
|
|
35
|
+
`filelist` is **included** too, on closer look: browsers *can* construct a
|
|
36
|
+
real `File` via `new File(...)`, and — checked directly in
|
|
37
|
+
`src/fundamentalTypes/filelistType.js` and `arrayType.js`'s `getValue` —
|
|
38
|
+
jsoe's own filelist control never actually requires a genuine
|
|
39
|
+
`instanceof FileList` for its internal value model; it already treats a
|
|
40
|
+
filelist's value as a plain array-like of `File` objects (built the same way
|
|
41
|
+
the `<input type="file" multiple>` change handler at `arrayType.js:2652-2684`
|
|
42
|
+
does, one `File` at a time via `$addAndSetArrayElement`). So no `FileList`
|
|
43
|
+
polyfill or extra dependency is needed: Typeson's existing `file` type-spec
|
|
44
|
+
(already in `structuredCloningJsoe`'s type list) round-trips `File` content
|
|
45
|
+
through the same encapsulate/revive path as everything else, and
|
|
46
|
+
`types.setValue({type: 'filelist', root, value})` accepts a plain array of
|
|
47
|
+
revived `File`s exactly as it does today. One caveat worth flagging in UI
|
|
48
|
+
copy or docs: `File` content typeson-encodes as base64 in the JSON6 text, so
|
|
49
|
+
large files will make for a very large edit/view buffer — acceptable for a
|
|
50
|
+
first pass, not a blocker.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const isRawEditable = types.showRawTypesonControls &&
|
|
54
|
+
((type === 'object' || type === 'array' ||
|
|
55
|
+
type === 'set' || type === 'map' || type === 'filelist') ||
|
|
56
|
+
recordMode || tupleMode);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 2. New dependencies
|
|
60
|
+
|
|
61
|
+
- **`json-6`** (not the unrelated, unmaintained `json6` package — confirmed
|
|
62
|
+
by inspecting both on the registry: `json6` is a dead 2015 prototype with no
|
|
63
|
+
`module`/ESM build; `json-6` is d3x0r's actual JSON6 implementation, matches
|
|
64
|
+
the GitHub project already linked from `README.md:124`, and ships an ESM
|
|
65
|
+
build at `dist/index.mjs`). API used: both `JSON6.parse(text)` and
|
|
66
|
+
`JSON6.stringify(value, replacer, space)` (default export). The README's
|
|
67
|
+
one-line description of `stringify` ("uses JSON stringify, so don't have to
|
|
68
|
+
replace") is misleading — reading `lib/json6.js:1195-1401` shows
|
|
69
|
+
`JSON6.stringify` is reassigned to a real, independent stringifier (not the
|
|
70
|
+
earlier `JSON.stringify` alias, which is just a bootstrapping placeholder
|
|
71
|
+
overwritten further down the file). It emits unquoted keys wherever a key
|
|
72
|
+
doesn't need quoting (`getIdentifier`, only quoting keywords, keys starting
|
|
73
|
+
with a digit/`-`, or keys containing whitespace/special characters), giving
|
|
74
|
+
genuinely simplified, more JS-object-literal-like output than
|
|
75
|
+
`JSON.stringify` would — a better fit for "syntax-highlighted typeson in
|
|
76
|
+
json6 format" as the *initial* text shown to the user. One behavioral quirk
|
|
77
|
+
to note: it also re-sorts object keys alphabetically rather than preserving
|
|
78
|
+
insertion order, which is harmless for round-tripping (the parser doesn't
|
|
79
|
+
care about key order) but worth being aware of if the display order looks
|
|
80
|
+
different from the form's own field order. `JSON6.stringify` produces the
|
|
81
|
+
text shown when the editor/viewer opens; `JSON6.parse` reads back whatever
|
|
82
|
+
superset syntax the user then edits it into (comments, unquoted keys,
|
|
83
|
+
trailing commas, etc.). Have asked the `json-6` author whether he may be
|
|
84
|
+
willing to accept a PR to stop the field sorting. If that isn't resolved
|
|
85
|
+
upstream in time, fall back to reordering `JSON6.stringify`'s output
|
|
86
|
+
ourselves post-hoc (walk the encapsulated value's own key order and reorder
|
|
87
|
+
the top level of each emitted object to match, a small local fixup) rather
|
|
88
|
+
than blocking this feature on the upstream change landing.
|
|
89
|
+
- **CodeMirror 6**: `codemirror` (meta-package bundling `basicSetup` +
|
|
90
|
+
`EditorView`/`EditorState`) and `@codemirror/lang-javascript` — used as the
|
|
91
|
+
language extension for *both* modes, not just eval. `@codemirror/lang-json`
|
|
92
|
+
is deliberately not used: it's a strict-JSON grammar and would mishighlight
|
|
93
|
+
(or just fail to recognize) JSON6's extensions — unquoted keys, comments,
|
|
94
|
+
trailing commas — that the safe/Typeson mode's text legitimately contains.
|
|
95
|
+
Since JSON6's syntax is (mostly) a subset of JS object-literal syntax, the
|
|
96
|
+
JS language extension already highlights it reasonably; this gets more
|
|
97
|
+
reliably true once the upstream `json-6` PR mentioned above (stop
|
|
98
|
+
alphabetical key sorting) — and ideally a second guarantee that its
|
|
99
|
+
`stringify` output is always valid ES/JS syntax, not just valid JSON6 — is
|
|
100
|
+
accepted, since JSON6 as a spec is intentionally a bit looser than JS in a
|
|
101
|
+
few corners.
|
|
102
|
+
- Both need the same vendoring treatment already established for
|
|
103
|
+
jamilih/typeson-registry/zod/zodexy/acorn/mime (`package.json:39-46`,
|
|
104
|
+
`src/vendor-imports.js`, demo `<script type="importmap">` blocks), so the
|
|
105
|
+
repo's own no-bundler demo pages keep working: add `copy-json6` and
|
|
106
|
+
`copy-codemirror` scripts copying the relevant `node_modules/**/dist` (and
|
|
107
|
+
CodeMirror's transitive `@lezer/*`, `style-mod`, `w3c-keyname`, `crelt`)
|
|
108
|
+
into `vendor/`, add matching bare-specifier re-exports in
|
|
109
|
+
`src/vendor-imports.js`, and add entries to each demo HTML's import map.
|
|
110
|
+
(npm consumers using their own bundler are unaffected either way, since
|
|
111
|
+
`exports`/`main` in `package.json` already point at unbundled `src/`.)
|
|
112
|
+
|
|
113
|
+
## 3. New constructor options: `allowUnsafeEval` and `showRawTypesonControls`
|
|
114
|
+
|
|
115
|
+
In `src/types.js`, add two new options alongside the existing
|
|
116
|
+
`useZodexyErrorMessages`/`useZodexyErrorMessagesInTypes` options
|
|
117
|
+
(`src/types.js:437-451`), assigning both onto `this`:
|
|
118
|
+
|
|
119
|
+
- `allowUnsafeEval = false` — gates the eval-mode sub-feature (see §4).
|
|
120
|
+
- `showRawTypesonControls = true` — a master on/off switch for the whole
|
|
121
|
+
feature. When `false`, neither the "Edit raw" nor "View raw" button is
|
|
122
|
+
rendered at all on any object/array control (including root) — the feature
|
|
123
|
+
is fully hidden, not merely disabled/greyed-out, for integrators who don't
|
|
124
|
+
want end users to see it. `allowUnsafeEval` has no effect when this is
|
|
125
|
+
`false`.
|
|
126
|
+
|
|
127
|
+
No further threading is required: `getUIForModeAndType` already passes
|
|
128
|
+
`types: this` into every `editUI`/`viewUI` call (`src/types.js:781,789`), and
|
|
129
|
+
`arrayType.js` already destructures `types` in both functions, so
|
|
130
|
+
`types.allowUnsafeEval`/`types.showRawTypesonControls` are immediately
|
|
131
|
+
readable where the buttons are built.
|
|
132
|
+
|
|
133
|
+
## 4. New shared module: `src/utils/rawTypesonEditor.js`
|
|
134
|
+
|
|
135
|
+
Extracting this out of `arrayType.js` (already 2771 lines) keeps the type
|
|
136
|
+
file's diff small. Exports:
|
|
137
|
+
|
|
138
|
+
- `getTypesonTextForValue(value, {types, format})` — builds a fresh
|
|
139
|
+
`new Typeson().register(structuredCloningJsoe)` (import
|
|
140
|
+
`structuredCloningJsoe` from `src/formats/structuredCloning.js`, the same
|
|
141
|
+
curated type-spec set the real format iteration uses — deliberately
|
|
142
|
+
*without* `functionSpec`, so the safe/Typeson text can never itself carry an
|
|
143
|
+
executable function tag), calls `await typeson.encapsulateAsync(value)`,
|
|
144
|
+
and returns `JSON6.stringify(encapsulated, null, 2)` for the simplified,
|
|
145
|
+
unquoted-key initial display text (see §2).
|
|
146
|
+
- `getValueForTypesonText(text, {types, format})` — `JSON6.parse(text)` then
|
|
147
|
+
`typeson.revive(parsed)` using the same registered instance/type-spec set.
|
|
148
|
+
Parse/revive errors propagate to the caller to show inline.
|
|
149
|
+
- `getValueForEvalText(text)` — only ever called when `allowUnsafeEval` is
|
|
150
|
+
true; `return (0, eval)('(' + text + ')');` (indirect eval, same "user
|
|
151
|
+
opted in" rationale as `structuredCloning.js:23-27`). This never touches
|
|
152
|
+
Typeson: the whole point of eval mode is that the user's text is a plain JS
|
|
153
|
+
expression like `{a: new Date()}` that already evaluates to the real value
|
|
154
|
+
directly, with no `$types` tagging or revive step involved.
|
|
155
|
+
- `getEvalSeedTextForValue(value)` — a **separate, dedicated** serializer used
|
|
156
|
+
only to seed the editor's text *when switching into eval mode*; it must not
|
|
157
|
+
reuse `getTypesonTextForValue`'s output. Typeson's encapsulated form tags
|
|
158
|
+
special values (e.g. a `Date` becomes an ISO-string field plus a `$types`
|
|
159
|
+
entry pointing at it); evaluating that tagged JSON6 text verbatim in eval
|
|
160
|
+
mode would hand back the plain tagged object, not a real `Date` — eval mode
|
|
161
|
+
has no revive step to undo the tagging. So this instead recursively emits
|
|
162
|
+
genuine JS constructor source for the same value — `new Date(<ISO string>)`,
|
|
163
|
+
`new Map([...])`, `new Set([...])`, a `RegExp`'s own literal form
|
|
164
|
+
(`/pattern/flags`), a function's own `toString()` (mirroring
|
|
165
|
+
`functionSpec.replace` in `structuredCloning.js:20-22`, which already does
|
|
166
|
+
exactly this for functions) — recursing into plain objects/arrays
|
|
167
|
+
structurally. A `Blob`/`File` gets the same treatment: its content is read
|
|
168
|
+
via `Blob#arrayBuffer()`, base64-encoded (chunked to avoid blowing the
|
|
169
|
+
argument-count limit `String.fromCodePoint(...bytes)` would hit spread over
|
|
170
|
+
a whole large file at once), and emitted as
|
|
171
|
+
`new Blob([Uint8Array.from(atob("..."), (c) => c.charCodeAt(0))], {type})`
|
|
172
|
+
(`new File([...], name, {type, lastModified})` for a `File`) — real,
|
|
173
|
+
independently-evaluable JS source, not Typeson's tagged form. This is the
|
|
174
|
+
one branch that's genuinely asynchronous (reading a `Blob`'s bytes always
|
|
175
|
+
returns a promise), which is why the whole function is `async` despite
|
|
176
|
+
every other branch being synchronous work. Scope the rest to the same
|
|
177
|
+
value shapes `structuredCloningJsoe` already covers; it's new, hand-rolled
|
|
178
|
+
code (no extra dependency needed — each case is a one-line template), kept
|
|
179
|
+
in this module specifically so it never gets confused with the
|
|
180
|
+
Typeson-tagged serializer.
|
|
181
|
+
- `openRawEditorDialog({types, format, root, type, topRoot, readonly})` —
|
|
182
|
+
builds the modal using the existing `src/utils/dialogs.js` helpers
|
|
183
|
+
(`makeSubmitDialog` for edit mode, `makeDialog`/`alert`-style for read-only
|
|
184
|
+
view mode, following the `fileType.js:201-241` pattern for structure).
|
|
185
|
+
Internals:
|
|
186
|
+
- Reads the *live* current value via
|
|
187
|
+
`types.getValueForRoot(root, {typeNamespace, formats: types.formats, format, types}, '')`
|
|
188
|
+
— using the control's own root element at click-time (not a stale
|
|
189
|
+
construction-time closure variable), since nested fields may have been
|
|
190
|
+
edited since the control was built.
|
|
191
|
+
- Mounts a CodeMirror `EditorView` (readonly in view mode via
|
|
192
|
+
`EditorState.readOnly.of(true)`/`EditorView.editable.of(false)`) inside
|
|
193
|
+
the dialog, using the `javascript()` language extension from
|
|
194
|
+
`@codemirror/lang-javascript` for both modes (Typeson/JSON6 and eval) —
|
|
195
|
+
see §2 on why `@codemirror/lang-json` is not used.
|
|
196
|
+
- In edit mode, if `types.allowUnsafeEval`, renders a small mode toggle
|
|
197
|
+
(radio/select) between "Typeson (JSON6)" and "JS (eval)". Switching modes
|
|
198
|
+
re-seeds the editor from the *original live value*, each mode using its
|
|
199
|
+
own serializer — `getTypesonTextForValue` for Typeson/JSON6,
|
|
200
|
+
`getEvalSeedTextForValue` for eval — rather than trying to textually
|
|
201
|
+
convert one mode's already-produced text into the other's syntax. This
|
|
202
|
+
means anything the user has typed but not yet saved is discarded on a
|
|
203
|
+
mode switch; that's an acceptable, clearly-scoped trade-off (the toggle is
|
|
204
|
+
for picking how to *start* editing, not for round-tripping in-progress
|
|
205
|
+
edits between the two syntaxes).
|
|
206
|
+
- On Save: parse via whichever mode is active
|
|
207
|
+
(`getValueForTypesonText`/`getValueForEvalText`) *before* touching the
|
|
208
|
+
underlying form tree at all. A parse failure — invalid JSON6 syntax from
|
|
209
|
+
`JSON6.parse`, a Typeson `revive` error, or a thrown/syntax-error `eval`
|
|
210
|
+
in unsafe mode — is caught and shown inline in the dialog; the dialog
|
|
211
|
+
stays open with the user's text untouched and **no existing DOM/control
|
|
212
|
+
is replaced or modified in any way**.
|
|
213
|
+
|
|
214
|
+
When the control has a `specificSchemaObject` (i.e. a schema is actually
|
|
215
|
+
driving this node), a successful parse is not sufficient on its own: the
|
|
216
|
+
resulting value must also pass that schema before Save is allowed to
|
|
217
|
+
proceed — reusing the existing schema-conformance check already used for
|
|
218
|
+
the `'schema'` format, `parseValue(types, schema, originalShape, value)`
|
|
219
|
+
in `src/formats/schema.js:203-210` (currently module-private; export it
|
|
220
|
+
for reuse here). A schema failure is reported inline the same way a parse
|
|
221
|
+
failure is — dialog stays open, nothing is written to the form. This is a
|
|
222
|
+
deliberately stricter gate than ordinary field-by-field editing (which
|
|
223
|
+
the rest of the app allows to sit in a transiently invalid state while
|
|
224
|
+
typing): a raw-text bulk replacement is a single commit action, not
|
|
225
|
+
incremental typing, so it's the right point to enforce the schema up
|
|
226
|
+
front rather than accept a bad bulk replacement and only flag it
|
|
227
|
+
afterward.
|
|
228
|
+
|
|
229
|
+
Only once parsing (and, where applicable, schema validation) succeeds
|
|
230
|
+
does the code proceed to call `types.setValue({type, root, value})` then
|
|
231
|
+
`types.validate({type, root, topRoot, avoidReport: false})` — per the
|
|
232
|
+
project rule that validity must always be recomputed and shown, never
|
|
233
|
+
suppressed, after a bulk value replacement — and close the dialog. This
|
|
234
|
+
means the existing object/array subtree in the form is swapped out only
|
|
235
|
+
on a successful parse; a bad edit never leaves the form in a
|
|
236
|
+
half-replaced or blanked state.
|
|
237
|
+
|
|
238
|
+
## 5. Changes to `src/fundamentalTypes/arrayType.js`
|
|
239
|
+
|
|
240
|
+
The eligibility check (§1) is computed identically in both functions, mirroring
|
|
241
|
+
the existing `recordMode`/`tupleMode` derivation already present in each.
|
|
242
|
+
|
|
243
|
+
- **`viewUI`** (~`arrayType.js:416-701`): when `isRawEditable`, add a plain
|
|
244
|
+
text button right after the existing collapse toggle (`arrayType.js:650-662`),
|
|
245
|
+
e.g. `['button', {class: 'viewRawTypeson', $on: {click () {...}}}, ['View raw']]`,
|
|
246
|
+
calling `openRawEditorDialog({..., readonly: true})`. Inserted *after* the
|
|
247
|
+
collapse button (not before) so `getInput`'s `$e(root, 'button')`
|
|
248
|
+
(`arrayType.js:702-705`, which grabs the first button for focus purposes)
|
|
249
|
+
keeps selecting the same element as today.
|
|
250
|
+
- **`editUI`** (~`arrayType.js:709-2767`): when `isRawEditable`, add the
|
|
251
|
+
analogous `['button', {class: 'editRawTypeson', ...}]` next to `minusButton`
|
|
252
|
+
at the `[..., minusButton, arrayContents]` assembly (`arrayType.js:2686-2687`),
|
|
253
|
+
calling `openRawEditorDialog({..., readonly: false})`.
|
|
254
|
+
- Both call sites pass `root: div` (the control's own `div.arrayHolder` /
|
|
255
|
+
`div[data-type]` element already in scope in each function), `type`,
|
|
256
|
+
`topRoot`, `types`, and `format`.
|
|
257
|
+
|
|
258
|
+
## 6. CSS
|
|
259
|
+
|
|
260
|
+
No new styling is required to match existing conventions — buttons in this
|
|
261
|
+
file are plain unstyled `<button>` elements with class names used only as
|
|
262
|
+
JS/test hooks (`arrayType.js:2139-2182` `addArrayElement`, confirmed no
|
|
263
|
+
matching CSS rule in `src/jsoe.css`). Add one small `.jsoe-raw-editor` rule to
|
|
264
|
+
`src/jsoe.css` sizing the CodeMirror host element, following the existing
|
|
265
|
+
`.view-binary, .view-text { width:300px; height:300px; }` convention
|
|
266
|
+
(`src/jsoe.css:137-140`).
|
|
267
|
+
|
|
268
|
+
## 7. Out of scope
|
|
269
|
+
|
|
270
|
+
- A real bidirectional syntax converter between the Typeson/JSON6 mode and
|
|
271
|
+
the eval mode's text — switching modes re-seeds from the original value
|
|
272
|
+
instead (§4).
|
|
273
|
+
- A `FileList` polyfill/construction library — not needed (§1).
|
|
274
|
+
- Constructing real, upstream-standard-conformant `FileList` instances at
|
|
275
|
+
all; jsoe's own filelist value model already tolerates a plain array-like
|
|
276
|
+
of `File`s, so this proposal doesn't change that.
|
|
277
|
+
|
|
278
|
+
## 8. Tests
|
|
279
|
+
|
|
280
|
+
- Unit tests for `getTypesonTextForValue`/`getValueForTypesonText`/
|
|
281
|
+
`getValueForEvalText`/`getEvalSeedTextForValue` round-tripping
|
|
282
|
+
representative values (plain object, array, nested Date/RegExp/Map/Set, and
|
|
283
|
+
— only under `allowUnsafeEval` — a function).
|
|
284
|
+
- Cypress specs exercising: the view-mode button opens a read-only highlighted
|
|
285
|
+
panel at root and at a nested object/array; the edit-mode button opens,
|
|
286
|
+
editing valid JSON6 text and saving updates the real form fields; invalid
|
|
287
|
+
syntax on Save shows an inline error and does not close the dialog; a
|
|
288
|
+
schema-driven control rejects a syntactically valid but schema-invalid
|
|
289
|
+
value the same way; the eval-mode option is absent unless `allowUnsafeEval`
|
|
290
|
+
is passed to `Types`, and, when present, evaluated text populates the
|
|
291
|
+
control; `showRawTypesonControls: false` hides both buttons everywhere.
|
|
292
|
+
Keep this work free of stray `console.log(obj)` lines — several specs stub
|
|
293
|
+
`window.console.log`.
|
|
294
|
+
|
|
295
|
+
## 9. Verification
|
|
296
|
+
|
|
297
|
+
1. `npm test` (unit) and the Cypress suite to confirm no regressions in
|
|
298
|
+
existing object/array editing flows.
|
|
299
|
+
2. Manually run the demo (`demo/index.html` or `demo/index-schema.html`),
|
|
300
|
+
exercise "View raw"/"Edit raw" at root and on a nested object/array, and
|
|
301
|
+
confirm the eval mode is absent (these pages construct a plain
|
|
302
|
+
`new Types()`). `demo/index-unsafe-eval.html` (+ its `-instrumented`
|
|
303
|
+
pair) is a dedicated demo for the opposite case: it constructs
|
|
304
|
+
`new Types({allowUnsafeEval: true})`, and its "Initialize with an object
|
|
305
|
+
containing a Date" button seeds a value so opening that object's
|
|
306
|
+
"Edit raw" button immediately shows the "Typeson (JSON6)"/"JS (eval)"
|
|
307
|
+
mode selector, letting you toggle between them and see the Date rendered
|
|
308
|
+
as Typeson-tagged JSON vs. real `new Date(...)` source.
|
|
309
|
+
3. Confirm `new Types({showRawTypesonControls: false})` renders no "Edit raw"/
|
|
310
|
+
"View raw" buttons anywhere in the tree (root or nested), and that the
|
|
311
|
+
default (option omitted) still shows them.
|
|
312
|
+
|
|
313
|
+
## Summary of the change surface
|
|
314
|
+
|
|
315
|
+
One new dependency pair (`json-6`, CodeMirror 6) plus their vendoring, two new
|
|
316
|
+
`Types` constructor options, one new util
|
|
317
|
+
(`src/utils/rawTypesonEditor.js`), an export added to
|
|
318
|
+
`src/formats/schema.js` (`parseValue`), and a button + eligibility check added
|
|
319
|
+
to both `viewUI` and `editUI` in `src/fundamentalTypes/arrayType.js`. No
|
|
320
|
+
existing behavior changes when the buttons are unused; `showRawTypesonControls`
|
|
321
|
+
lets integrators opt out entirely.
|
|
322
|
+
|
|
323
|
+
## Implementation notes (corrections found while building)
|
|
324
|
+
|
|
325
|
+
Three things in this proposal turned out to be wrong or incomplete once
|
|
326
|
+
actually implemented and exercised against real Typeson output and a real
|
|
327
|
+
browser:
|
|
328
|
+
|
|
329
|
+
1. **Object/array type objects have no `setValue`.** `types.setValue({type,
|
|
330
|
+
root, value})` (§4's original plan for "Save") is a silent no-op for
|
|
331
|
+
`object`/`array`/`set`/`map`/`filelist` — only leaf types implement
|
|
332
|
+
`setValue`; a container's children are always attached one at a time via
|
|
333
|
+
`$addAndSetArrayElement`, driven by walking a Typeson `encapsulateAsync`
|
|
334
|
+
pass (`src/formats/structuredCloning.js`'s `encapsulateObserver`/
|
|
335
|
+
`iterate`). "Save" therefore needed a real `commitValueToContainer`
|
|
336
|
+
helper that reuses that exact walk, scoped to just the edited container,
|
|
337
|
+
rather than a single `setValue` call.
|
|
338
|
+
|
|
339
|
+
Reusing it required one small, behavior-preserving change to
|
|
340
|
+
`encapsulateObserver` in `src/formats/structuredCloning.js`: the branch
|
|
341
|
+
that registers a walked value's root as an attachment point
|
|
342
|
+
(`parents['']`) previously only ran `if (!stateObj.rootUI)` — i.e. only
|
|
343
|
+
when building a brand-new root. Changed to `if (!parents[''])`, with the
|
|
344
|
+
`getUIForModeAndType` call to build a *new* root now nested inside and
|
|
345
|
+
conditioned on `!stateObj.rootUI` — so a caller can pre-seed
|
|
346
|
+
`stateObj.rootUI` with an *existing* element (our control's own root) and
|
|
347
|
+
still have it correctly registered, letting the same walk populate an
|
|
348
|
+
existing container's children instead of only ever building a fresh one.
|
|
349
|
+
This matters because the edited container's root element must keep its
|
|
350
|
+
identity — other code (crucially, the root of the whole form) may already
|
|
351
|
+
hold a reference to it.
|
|
352
|
+
|
|
353
|
+
That in turn surfaced a second gap: the container's own
|
|
354
|
+
`itemIndex`/legend-numbering counter, closed over since it was first
|
|
355
|
+
built, wasn't reset by any of this, so manually adding an item afterward
|
|
356
|
+
(the "+ Item" button) could get a legend number continuing from the
|
|
357
|
+
*replaced* content instead of the new content (data was never affected,
|
|
358
|
+
only the numbering shown for a subsequently-added item). Fixed by adding
|
|
359
|
+
`$resetItemIndex()` — a new `$custom` method on the container alongside
|
|
360
|
+
`$addAndSetArrayElement`/`$getArrayItems` in `arrayType.js` —
|
|
361
|
+
`commitValueToContainer` calls it right after clearing the old children
|
|
362
|
+
and before repopulating. (One subtlety found while writing this:
|
|
363
|
+
`arrayNonindexKeys`, being sparse, already self-heals `itemIndex` by
|
|
364
|
+
recomputing it from the live DOM on every "+ Item" click, so it doesn't
|
|
365
|
+
exercise this bug — plain `object`/`array`, which just do `itemIndex++`
|
|
366
|
+
obliviously, do.)
|
|
367
|
+
|
|
368
|
+
The first version of this fix reset to `-1` unconditionally, copying the
|
|
369
|
+
existing "x All" button's hardcoded reset value — which is only correct
|
|
370
|
+
for arrays (whose baseline really is `-1`); an *object*'s baseline is `0`
|
|
371
|
+
(`itemAdjust - 1`, the exact value `itemIndex` is initialized to before
|
|
372
|
+
any item is ever added). Manual testing on `demo/index-unsafe-eval.html`
|
|
373
|
+
caught the resulting regression directly: adding a single property (shown
|
|
374
|
+
as "1", correctly) and then changing only its *value* via a raw eval edit
|
|
375
|
+
— never touching its name or the property count — renumbered it to "0".
|
|
376
|
+
`$resetItemIndex()` now resets to `itemAdjust - 1` (matching the
|
|
377
|
+
container's own true starting baseline) rather than the array-specific
|
|
378
|
+
`-1`, so repopulating N unchanged properties leaves them numbered exactly
|
|
379
|
+
as a fresh container holding those same N properties would be — which,
|
|
380
|
+
for an edit that doesn't change the property count, is identical to what
|
|
381
|
+
they were numbered before the edit.
|
|
382
|
+
|
|
383
|
+
2. **`json-6`'s `stringify` has a real bug beyond the key-sorting one**,
|
|
384
|
+
found by actually round-tripping values: it runs the same
|
|
385
|
+
identifier-unquoting logic it correctly uses for object *keys* on string
|
|
386
|
+
*values* too, so `{b: "hello"}` stringified to `{b: hello}` — a bareword
|
|
387
|
+
that parses back as an identifier reference, not the string, and wasn't
|
|
388
|
+
even re-parseable by the package's own `JSON6.parse`. `getTypesonTextForValue`
|
|
389
|
+
therefore does not call `JSON6.stringify` at all; it uses a small
|
|
390
|
+
hand-rolled `stringifyJSON6` (in `src/utils/rawTypesonEditor.js`) that
|
|
391
|
+
unquotes identifier-shaped keys but always fully quotes string values via
|
|
392
|
+
`JSON.stringify`. `JSON6.parse` (used to read the text back) is unaffected
|
|
393
|
+
and works correctly. A fix for the value-quoting bug (not the key-sorting
|
|
394
|
+
one, which stays a separate future PR per the maintainer's fork owner) was
|
|
395
|
+
prepared as a commit on the `fix/stringify-value-quoting` branch in the
|
|
396
|
+
local `json-6` fork checkout, with accompanying test cases, ready to
|
|
397
|
+
submit upstream — jsoe's own code keeps its local workaround regardless of
|
|
398
|
+
whether/when that lands, since a published jsoe cannot assume a consumer's
|
|
399
|
+
copy of `json-6` is patched.
|
|
400
|
+
|
|
401
|
+
3. **`typeson.encapsulateAsync(value)` throws `TypeError: Async method
|
|
402
|
+
requested but sync result obtained`** for a value with no actually-async
|
|
403
|
+
parts (no `Blob`/`File`/`Promise`) unless called with
|
|
404
|
+
`{throwOnBadSyncType: false}` — exactly the option
|
|
405
|
+
`src/formats/structuredCloning.js`'s own `iterate()` already passes.
|
|
406
|
+
`getTypesonTextForValue` now passes the same option.
|
|
407
|
+
|
|
408
|
+
4. **`npm run tsc-cypress` had four pre-existing failures**, unrelated to this
|
|
409
|
+
feature but fixed at the source rather than left as "pre-existing" once
|
|
410
|
+
noticed, since they were genuine gaps rather than acceptable noise:
|
|
411
|
+
- A test's `/** @type {import('zodexy').SzUnion} */` cast on a 2-option
|
|
412
|
+
union literal silently relied on `SzUnion`'s default generic, which is a
|
|
413
|
+
1-element tuple (`SzUnion<[SzType]> `); parametrized it explicitly as
|
|
414
|
+
`SzUnion<[SzType, SzType]>` to match the actual literal.
|
|
415
|
+
- `src/typeChoices.js`'s `TypeChoicesElementAPI` typedef was missing three
|
|
416
|
+
methods (`$setType`, `$getValue`, `$addTypeAndEditUI`) that the
|
|
417
|
+
`$custom` object backing every type-choices `<select>` genuinely
|
|
418
|
+
implements — a real gap in the type, not a test-only issue. Added them.
|
|
419
|
+
- That in turn exposed a second, more interesting gap: `domArray`'s
|
|
420
|
+
`select` slot (and `buildXorTypeChoices`'s return) were typed as
|
|
421
|
+
`TypeChoicesElementAPI`, i.e. `HTMLSelectElement & {...methods}` — but
|
|
422
|
+
`buildXorTypeChoices` actually returns a real `<fieldset>` with
|
|
423
|
+
`value`/`selectedIndex`/`selectedOptions` shimmed on via
|
|
424
|
+
`Object.defineProperties` and the `$`-methods copied over by reference,
|
|
425
|
+
standing in for a `<select>` for the `xor` (exclusive union) case —
|
|
426
|
+
never an actual `HTMLSelectElement`. Asserting `HTMLSelectElement` there
|
|
427
|
+
was a false claim that merely happened not to be exercised strictly
|
|
428
|
+
enough to fail until the missing-methods gap above was fixed and
|
|
429
|
+
`tsc-cypress` type-checked deeper. Fixed by splitting the `$`-methods
|
|
430
|
+
out into their own `TypeChoicesAPIMethods` type, keeping
|
|
431
|
+
`TypeChoicesElementAPI` (`HTMLSelectElement & TypeChoicesAPIMethods`)
|
|
432
|
+
for the real `<select>`, and adding `TypeChoicesControl`
|
|
433
|
+
(`HTMLElement & TypeChoicesAPIMethods & {value, selectedIndex,
|
|
434
|
+
selectedOptions}`) — accurately describing only the shimmed surface
|
|
435
|
+
downstream code actually relies on — for anywhere a `xor` fieldset can
|
|
436
|
+
stand in for the select (`domArray`, `buildXorTypeChoices`'s
|
|
437
|
+
parameter/return).
|
|
438
|
+
- A `getUIForModeAndType` test call was missing the required `value`/
|
|
439
|
+
`hasValue` properties; added `value: undefined, hasValue: false`,
|
|
440
|
+
matching how another test in the same file already builds an
|
|
441
|
+
unseeded control.
|
|
442
|
+
- Separately, `cypress/e2e/util-unit-tests/rawTypesonEditor.cy.js`
|
|
443
|
+
originally imported `Types` via `#jsoe/index.js`'s re-export, which
|
|
444
|
+
`tsc-cypress` treated as a nominally different `Types` declaration than
|
|
445
|
+
the one `commitValueToContainer`'s own JSDoc references via a relative
|
|
446
|
+
`'../types.js'` path — surfacing as a confusing
|
|
447
|
+
"Type 'Types' is missing ... from type 'Types'" error. Importing
|
|
448
|
+
`Types` directly from `#jsoe/types.js` instead (matching the existing
|
|
449
|
+
convention in `cypress/e2e/src/types.cy.js`) resolved it.
|
|
450
|
+
|
|
451
|
+
`npm run tsc-cypress` is now fully clean, with no remaining pre-existing
|
|
452
|
+
errors.
|
|
453
|
+
|
|
454
|
+
5. **`getEvalSeedTextForValue` originally fell through to an unquoted
|
|
455
|
+
`String(value)` or a same-shaped-but-wrong `{}` literal for several value
|
|
456
|
+
shapes it didn't explicitly recognize**, rather than either reconstructing
|
|
457
|
+
them correctly or failing clearly. A follow-up pass ("low-hanging fruit")
|
|
458
|
+
added explicit handling for the easy/mechanical cases:
|
|
459
|
+
- `Symbol` — previously emitted a bare, unquoted, unparseable
|
|
460
|
+
`Symbol(desc)`-as-identifier-reference; now emits real
|
|
461
|
+
`Symbol("desc")`/`Symbol.for("key")` source, using the same
|
|
462
|
+
`String(sym).slice(7, -1)` convention `src/fundamentalTypes/symbolType.js`
|
|
463
|
+
itself uses (not `.description`, which is `undefined` for a
|
|
464
|
+
no-description `Symbol()`).
|
|
465
|
+
- Boxed primitives (`new String(...)`/`new Number(...)`/`new Boolean(...)`/
|
|
466
|
+
boxed `BigInt`) — previously treated identically to their primitive
|
|
467
|
+
forms (losing the boxing); now emit the equivalent `new` expression
|
|
468
|
+
(`Object(5n)` for boxed `BigInt`, matching typeson-registry's own
|
|
469
|
+
`bigintObject` revive convention).
|
|
470
|
+
- `DOMException`, `DOMRect`/`DOMRectReadOnly`, `DOMPoint`/
|
|
471
|
+
`DOMPointReadOnly`, `DOMMatrix`/`DOMMatrixReadOnly` — each now emits its
|
|
472
|
+
real constructor call with its own field values (a `DOMMatrix` branches
|
|
473
|
+
on `m.is2D` to emit either the 6-number or 16-number constructor form,
|
|
474
|
+
matching `src/superTypes/dommatrixType.js`).
|
|
475
|
+
- `ArrayBuffer`, `DataView`, and all typed array types (including
|
|
476
|
+
`Float16Array`, added after being noticed missing from the initial
|
|
477
|
+
`typedArrayTagNames` list) — bytes are copied synchronously (no async
|
|
478
|
+
read needed, unlike `Blob`/`File`) and base64-encoded via a new shared
|
|
479
|
+
`bytesToBase64` helper, emitted as `Uint8Array.from(atob("..."), (c) =>
|
|
480
|
+
c.charCodeAt(0)).buffer` (bare for `ArrayBuffer`) or wrapped in `new
|
|
481
|
+
DataView(...)`/`new Int8Array(...)`/etc.
|
|
482
|
+
- `FileList` — treated as a plain array of `File`s for eval-source
|
|
483
|
+
purposes (spread into a real array first), consistent with §1's finding
|
|
484
|
+
that jsoe's own filelist control never requires a genuine
|
|
485
|
+
`instanceof FileList`.
|
|
486
|
+
- `Promise` and any value whose prototype isn't `Object.prototype`/`null`
|
|
487
|
+
(an unrecognized class instance) now throw a clear, named `TypeError`
|
|
488
|
+
instead of silently falling through to a same-shaped-but-wrong `{}` or
|
|
489
|
+
unquoted `String(value)` — eval mode has no way to reconstruct a
|
|
490
|
+
pending/settled `Promise`'s state or an arbitrary class's behavior, and a
|
|
491
|
+
loud failure pointing at the Typeson/JSON6 mode instead is far better
|
|
492
|
+
than a value that looks right but silently isn't.
|
|
493
|
+
|
|
494
|
+
Two of these surfaced their own bugs only once actually run (type-checking
|
|
495
|
+
alone didn't catch either, since both are about which *string* ends up in
|
|
496
|
+
the output, not its type):
|
|
497
|
+
- `Object.prototype.toString` (and thus this codebase's own `toStringTag`
|
|
498
|
+
helper) reports **every** `Error` subclass — `TypeError`, `RangeError`,
|
|
499
|
+
`AggregateError`, etc. — as `[object Error]`; this is a spec'd
|
|
500
|
+
`[[ErrorData]]`-internal-slot check, not a `Symbol.toStringTag` lookup,
|
|
501
|
+
so it can't be worked around by reading a tag property. A `new
|
|
502
|
+
TypeError('bad type')` was therefore being re-emitted as `new
|
|
503
|
+
Error('bad type')`, silently downgrading the reconstructed error's own
|
|
504
|
+
type. Fixed by recovering the real constructor name from
|
|
505
|
+
`err.constructor.name` instead of `tag`, falling back to plain `Error`
|
|
506
|
+
only when that name isn't one eval mode actually knows how to
|
|
507
|
+
name-construct (guards against a user-defined `class MyError extends
|
|
508
|
+
Error {}`, which isn't globally nameable this way).
|
|
509
|
+
- The same tag-collapsing issue affected the unrecognized-class-instance
|
|
510
|
+
error message: `toStringTag` reports a generic `Object` for most
|
|
511
|
+
user-defined classes (they don't set their own `Symbol.toStringTag`), so
|
|
512
|
+
the thrown message read "...cannot represent a Object value..." instead
|
|
513
|
+
of naming the actual class. Fixed by preferring
|
|
514
|
+
`value.constructor?.name` over `tag` when building that message.
|
|
515
|
+
|
|
516
|
+
Both were caught by running the corresponding new Cypress tests (not by
|
|
517
|
+
type-checking, which was clean throughout) — `expected [TypeError: bad
|
|
518
|
+
type] to be an instance of TypeError` and a message-content assertion
|
|
519
|
+
expecting `/Whatever/u` that got the generic `Object` text instead.
|
|
520
|
+
|
|
521
|
+
Validated: `npx eslint .`, `npx tsc`, `npm run tsc:ts7` (TypeScript 7 nightly),
|
|
522
|
+
and `npm run tsc-cypress` are all clean. `cypress/e2e/util-unit-tests/rawTypesonEditor.cy.js`
|
|
523
|
+
(22 tests, run against a real Chrome via Cypress, all passing) covers:
|
|
524
|
+
- Typeson/JSON6 and eval-mode round-tripping, including Date/RegExp/Map/Set,
|
|
525
|
+
quoted-vs-unquoted keys, hand-edited JSON6 syntax (comments) being accepted
|
|
526
|
+
back, a `File` and a plain `Blob` each round-tripping through eval with
|
|
527
|
+
their real binary content (name/type/`lastModified` and bytes all
|
|
528
|
+
verified, via `evaled.text()`), and a cyclic value failing clearly in eval
|
|
529
|
+
mode;
|
|
530
|
+
- The low-hanging-fruit eval-mode fixes above: a plain and a registered
|
|
531
|
+
`Symbol` round-tripping to the same symbol (via `Symbol.for`); boxed
|
|
532
|
+
`String`/`Number`/`Boolean`/`BigInt` round-tripping as real boxed objects;
|
|
533
|
+
a `TypeError` and an `AggregateError` (with `cause`, and with its nested
|
|
534
|
+
`errors` array) round-tripping as their real, specific constructors, not a
|
|
535
|
+
generic `Error`; `DOMException`/`DOMRect`/`DOMPoint`/`DOMMatrix`
|
|
536
|
+
round-tripping their fields and `instanceof`; `ArrayBuffer`/`DataView`/a
|
|
537
|
+
typed array round-tripping their real bytes; a `FileList` (built via a real
|
|
538
|
+
`DataTransfer`) round-tripping as a plain array of `File`s; and a `Promise`
|
|
539
|
+
and a user-defined class instance each throwing a clear, correctly-named
|
|
540
|
+
error instead of silently producing a wrong value;
|
|
541
|
+
- `commitValueToContainer`'s `$resetItemIndex` fix, two ways: (1) builds a
|
|
542
|
+
real 3-property object control, replaces it via `commitValueToContainer`
|
|
543
|
+
with a single property, then clicks the real "+ Item" button and asserts
|
|
544
|
+
both the repopulated property's own legend number and the new manually-
|
|
545
|
+
added property's legend number match what a fresh container with that
|
|
546
|
+
many properties would show; (2) the exact regression manual testing
|
|
547
|
+
found — a single manually-added property (legend "1") whose *value* alone
|
|
548
|
+
is changed via `commitValueToContainer` (reading the live value via
|
|
549
|
+
`types.getValueForRoot`, exactly as `openRawEditorDialog` does, and
|
|
550
|
+
writing back the same key with a new value) keeps showing "1" afterward;
|
|
551
|
+
- `allowUnsafeEval` gating, driven through the real "Edit raw" button and
|
|
552
|
+
dialog: the eval-mode `<select>` is absent by default, present when
|
|
553
|
+
`allowUnsafeEval: true`, and switching to it reseeds the editor with real
|
|
554
|
+
JS constructor source (`new Date(`) instead of Typeson-tagged data
|
|
555
|
+
(`$types`).
|
|
556
|
+
|
|
557
|
+
The existing 59-test `cypress/e2e/fundamentalTypes/array.cy.js` suite still
|
|
558
|
+
passes unchanged throughout, confirming no regression to existing
|
|
559
|
+
object/array/tuple/record behavior.
|
|
560
|
+
|
|
561
|
+
One environment quirk worth noting for future tests in this file: none of
|
|
562
|
+
these specs call `cy.visit()` (matching this directory's existing style,
|
|
563
|
+
e.g. `dialogs.cy.js`), which means there is no application document for
|
|
564
|
+
`cy.get(cssSelector)` to search — elements appended via a plain
|
|
565
|
+
`document.body.append(...)` are invisible to it and any `cy.get()` call
|
|
566
|
+
against a selector will simply time out. The fix used throughout is to hold
|
|
567
|
+
the actual element reference (via `.as('alias')`) and query relative to it
|
|
568
|
+
with `cy.wrap(...)`/`cy.get('@alias').find(...)`, which operate on that
|
|
569
|
+
specific element's own subtree directly rather than searching a page. A
|
|
570
|
+
related wrinkle: action commands like `.click()`/`.select()` refuse to act on
|
|
571
|
+
elements outside cypress's (here, blank/unused) viewport; `{force: true}` is
|
|
572
|
+
disallowed by this repo's lint config (`sonarjs/no-forced-browser-interaction`),
|
|
573
|
+
so these tests instead call `.invoke('click')` or manually set `.value` and
|
|
574
|
+
dispatch a `change` event, matching the plain-DOM-interaction style
|
|
575
|
+
`dialogs.cy.js` already uses for its own dialogs.
|