@unbound-app/api 0.11.0 → 1.1.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.
Files changed (72) hide show
  1. package/ambient/image-modules.d.ts +58 -0
  2. package/package.json +27 -4
  3. package/src/_internal.d.ts +2771 -139
  4. package/src/{utils.d.ts → _types.d.ts} +10 -0
  5. package/src/assets.d.ts +64 -26
  6. package/src/builtins.d.ts +17 -0
  7. package/src/commands.d.ts +101 -25
  8. package/src/dialogs.d.ts +90 -25
  9. package/src/flux.d.ts +317 -0
  10. package/src/fonts.d.ts +51 -0
  11. package/src/fs.d.ts +15 -24
  12. package/src/global.d.ts +173 -11
  13. package/src/i18n.d.ts +33 -31
  14. package/src/iconPacks.d.ts +43 -0
  15. package/src/index.d.ts +14 -0
  16. package/src/marketplace.d.ts +50 -0
  17. package/src/metro/api.d.ts +8 -0
  18. package/src/metro/common.d.ts +34 -5
  19. package/src/metro/components.d.ts +34 -25
  20. package/src/metro/filters.d.ts +31 -16
  21. package/src/metro/index.d.ts +26079 -83
  22. package/src/metro/stores.d.ts +5 -0
  23. package/src/native.d.ts +77 -5
  24. package/src/patcher.d.ts +214 -57
  25. package/src/plugins.d.ts +55 -0
  26. package/src/settings.d.ts +8 -16
  27. package/src/shims.d.ts +5 -0
  28. package/src/storage.d.ts +80 -224
  29. package/src/sync.d.ts +51 -0
  30. package/src/themes.d.ts +43 -0
  31. package/src/toasts.d.ts +62 -9
  32. package/src/ui.d.ts +650 -0
  33. package/src/updates.d.ts +25 -0
  34. package/src/utils/index.d.ts +53 -0
  35. package/src/utils/react/index.d.ts +82 -0
  36. package/testing/fake-api.ts +256 -0
  37. package/testing/fake-components.ts +183 -0
  38. package/testing/fake-feedback.ts +258 -0
  39. package/testing/fake-fs.ts +54 -0
  40. package/testing/fake-java.ts +513 -0
  41. package/testing/fake-locales.ts +85 -0
  42. package/testing/fake-metro.ts +346 -0
  43. package/testing/fake-native-bridge.ts +98 -0
  44. package/testing/fake-native.ts +211 -0
  45. package/testing/fake-objc.ts +388 -0
  46. package/testing/fake-plugins.ts +176 -0
  47. package/testing/fake-react-native-modules.ts +362 -0
  48. package/testing/fake-react-native.ts +180 -0
  49. package/testing/fake-react.ts +54 -0
  50. package/testing/fake-settings-panel.ts +173 -0
  51. package/testing/fake-settings.ts +428 -0
  52. package/testing/fake-timers.ts +74 -0
  53. package/testing/fake-utils.ts +216 -0
  54. package/testing/icu-format.ts +132 -0
  55. package/testing/icu-parse.ts +277 -0
  56. package/testing/index.ts +42 -0
  57. package/testing/install-addon-globals.ts +260 -0
  58. package/testing/listener-registry.ts +39 -0
  59. package/testing/memory-storage.ts +149 -0
  60. package/testing/plugin-files.ts +155 -0
  61. package/testing/plugin-lifecycle.ts +149 -0
  62. package/testing/plugin-types.ts +34 -0
  63. package/testing/preset.ts +263 -0
  64. package/testing/react-patcher.ts +154 -0
  65. package/testing/react-value.ts +3 -0
  66. package/testing/recorders.ts +183 -0
  67. package/testing/recording-patcher.ts +173 -0
  68. package/testing/render.ts +84 -0
  69. package/testing/session.ts +77 -0
  70. package/testing/test-node.ts +163 -0
  71. package/testing/test-renderer.ts +753 -0
  72. package/testing/unfrozen-elements.ts +49 -0
@@ -0,0 +1,258 @@
1
+ import type { AlertDialogProps, DialogHandle } from '../src/dialogs';
2
+ import type { ToastHandle, ToastOptions } from '../src/toasts';
3
+ import type { IcuVars } from './icu-format';
4
+
5
+ import { parseIcu } from './icu-parse';
6
+
7
+ /** How the fakes resolve a plugin's message keys, as the client's scoped `resolveMessage` does. */
8
+ export type FakeMessageLookup = {
9
+ resolveMessage: (key: string, vars?: IcuVars) => string;
10
+ /** Whether the key's en-US message has placeholders, so the argument after it is its vars. */
11
+ takesVars: (key: string) => boolean;
12
+ };
13
+
14
+ /** A toast a plugin showed: its options, and the key and vars of a shorthand's title. */
15
+ export type ShownToast = ToastOptions & { key?: string; vars?: IcuVars };
16
+
17
+ /** The fake `toasts`: each toast shown is recorded in `shown`, and its handle's `update` edits the record. */
18
+ export type FakeToasts = {
19
+ shown: ShownToast[];
20
+ show: (options: ToastOptions) => ToastHandle;
21
+ success: (key: string, ...args: unknown[]) => ToastHandle;
22
+ info: (key: string, ...args: unknown[]) => ToastHandle;
23
+ warning: (key: string, ...args: unknown[]) => ToastHandle;
24
+ error: (...args: unknown[]) => ToastHandle;
25
+ };
26
+
27
+ /** What `confirm` and `alert` take after their title key. */
28
+ export type DialogShorthandOptions = {
29
+ titleVars?: IcuVars;
30
+ body?: string;
31
+ bodyVars?: IcuVars;
32
+ confirm?: string;
33
+ cancel?: string;
34
+ destructive?: boolean;
35
+ };
36
+
37
+ /** A dialog a plugin opened, and how a test answers it. */
38
+ export type OpenedDialog = {
39
+ kind: 'dialog' | 'confirm' | 'alert';
40
+ /** The title as shown, resolved from its key for `confirm` and `alert`. */
41
+ title: string;
42
+ body: string | undefined;
43
+ /** The title's message key, for `confirm` and `alert`. */
44
+ key: string | undefined;
45
+ options: AlertDialogProps | DialogShorthandOptions;
46
+ /** Whether it is still showing. */
47
+ open: boolean;
48
+ /**
49
+ * Answers it: `true` confirms or presses the first button, `false` cancels, and a string presses the button with
50
+ * that label.
51
+ */
52
+ respond: (answer: boolean | string) => void;
53
+ };
54
+
55
+ /** The fake `dialogs`: each dialog opened is recorded in `opened`, to `respond` to. */
56
+ export type FakeDialogs = {
57
+ opened: OpenedDialog[];
58
+ showDialog: (options: AlertDialogProps) => DialogHandle;
59
+ confirm: (key: string, options: DialogShorthandOptions) => Promise<boolean>;
60
+ alert: (key: string, options?: DialogShorthandOptions) => Promise<void>;
61
+ dismissAll: () => void;
62
+ start: () => void;
63
+ stop: () => void;
64
+ };
65
+
66
+ /** Keys shown as themselves, for a plugin without `i18n/en-US.json`. */
67
+ export const KEY_LOOKUP: FakeMessageLookup = {
68
+ /**
69
+ * @description Shows a key as itself.
70
+ * @param key The message key.
71
+ * @returns The key.
72
+ */
73
+ resolveMessage: (key) => key,
74
+ /**
75
+ * @description Says no key has placeholders, since there is no en-US file to read them from.
76
+ * @returns `false`.
77
+ */
78
+ takesVars: () => false,
79
+ };
80
+
81
+ // Unbound's own labels the shorthands fall back to, in en-US.
82
+ const CORE = { ok: 'OK', errorTitle: 'Something went wrong' };
83
+
84
+ /**
85
+ * @description Tells whether a message has a placeholder, as the client decides whether a key takes vars.
86
+ * @param message The en-US message, if the key has one.
87
+ * @returns Whether it has an argument, plural or select.
88
+ */
89
+ export function hasPlaceholders(message: string | undefined): boolean {
90
+ return message !== undefined && parseIcu(message).some((node) => node.kind !== 'text');
91
+ }
92
+
93
+ function isVars(value: unknown): value is IcuVars {
94
+ return typeof value === 'object' && value !== null && !(value instanceof Error);
95
+ }
96
+
97
+ // The key's vars come right after it when its message has placeholders, as the typings order them.
98
+ // UNKNOWN: a shorthand's arguments after its key are vars, options or an error, which this sorts out.
99
+ function takeMessage(lookup: FakeMessageLookup, key: string, args: readonly unknown[]) {
100
+ if (!lookup.takesVars(key)) {
101
+ return { key, vars: undefined, text: lookup.resolveMessage(key), rest: [...args] };
102
+ }
103
+
104
+ const [first, ...rest] = args;
105
+ const vars = isVars(first) ? first : undefined;
106
+
107
+ return { key, vars, text: lookup.resolveMessage(key, vars), rest };
108
+ }
109
+
110
+ // UNKNOWN: the argument after a shorthand's vars is its options, or nothing.
111
+ function pickOptions(value: unknown): Partial<ToastOptions> {
112
+ return isVars(value) ? value : {};
113
+ }
114
+
115
+ function errorText(cause: unknown): string {
116
+ return cause instanceof Error ? cause.message : String(cause);
117
+ }
118
+
119
+ /**
120
+ * @description Creates the fake `toasts`, whose shorthands resolve keys through `lookup` as the client's do.
121
+ * @param lookup Reads the plugin's keys; called on each toast, so it may change after creation.
122
+ * @returns The toasts.
123
+ */
124
+ export function createFakeToasts(lookup: () => FakeMessageLookup = () => KEY_LOOKUP): FakeToasts {
125
+ const shown: ShownToast[] = [];
126
+
127
+ function show(options: ShownToast): ToastHandle {
128
+ const record = { ...options };
129
+
130
+ shown.push(record);
131
+
132
+ return { update: (next) => void Object.assign(record, next), close: () => {} };
133
+ }
134
+
135
+ function variant(key: string, ...args: unknown[]) {
136
+ const { text, vars, rest } = takeMessage(lookup(), key, args);
137
+
138
+ return show({ ...pickOptions(rest[0]), title: text, key, vars });
139
+ }
140
+
141
+ // One argument is always the error; with more, the first is the title key.
142
+ function error(...args: unknown[]) {
143
+ if (args.length === 1) {
144
+ return show({ title: CORE.errorTitle, content: errorText(args[0]) });
145
+ }
146
+
147
+ const { text, vars, rest } = takeMessage(lookup(), String(args[0]), args.slice(1));
148
+
149
+ return show({
150
+ ...pickOptions(rest[1]),
151
+ title: text,
152
+ content: errorText(rest[0]),
153
+ key: String(args[0]),
154
+ vars,
155
+ });
156
+ }
157
+
158
+ return { shown, show, success: variant, info: variant, warning: variant, error };
159
+ }
160
+
161
+ /**
162
+ * @description Creates the fake `dialogs`, whose `confirm` and `alert` resolve keys through `lookup`.
163
+ * @param lookup Reads the plugin's keys; called on each dialog, so it may change after creation.
164
+ * @returns The dialogs.
165
+ */
166
+ export function createFakeDialogs(lookup: () => FakeMessageLookup = () => KEY_LOOKUP): FakeDialogs {
167
+ const opened: OpenedDialog[] = [];
168
+
169
+ function open(
170
+ dialog: Omit<OpenedDialog, 'open' | 'respond'>,
171
+ answer: (value: boolean | string) => void,
172
+ ) {
173
+ const record: OpenedDialog = {
174
+ ...dialog,
175
+ open: true,
176
+ respond(value) {
177
+ if (!record.open) {
178
+ throw new Error(`Failed to respond to dialog "${record.title}": it is closed.`);
179
+ }
180
+
181
+ record.open = false;
182
+ answer(value);
183
+ },
184
+ };
185
+
186
+ opened.push(record);
187
+
188
+ return record;
189
+ }
190
+
191
+ function resolve(key: string | undefined, vars?: IcuVars) {
192
+ return key === undefined ? undefined : lookup().resolveMessage(key, vars);
193
+ }
194
+
195
+ function dismissAll() {
196
+ for (const dialog of opened)
197
+ if (dialog.open) {
198
+ dialog.respond(false);
199
+ }
200
+ }
201
+
202
+ return {
203
+ opened,
204
+ showDialog(options) {
205
+ const record = open(
206
+ { kind: 'dialog', title: options.title, body: options.body, key: void 0, options },
207
+ (answer) => {
208
+ const buttons = options.buttons ?? [];
209
+ const pressed =
210
+ typeof answer === 'string'
211
+ ? buttons.find((button) => button.label === answer)
212
+ : answer
213
+ ? buttons[0]
214
+ : void 0;
215
+
216
+ if (pressed) {
217
+ pressed.onPress?.();
218
+ } else {
219
+ options.onCancel?.();
220
+ }
221
+ },
222
+ );
223
+
224
+ return {
225
+ key: `unbound-dialog-${opened.length}`,
226
+ dismiss: () => void (record.open = false),
227
+ };
228
+ },
229
+ confirm: (key, options) =>
230
+ new Promise((done) => {
231
+ const confirm = resolve(options.confirm) ?? CORE.ok;
232
+ const dialog = {
233
+ kind: 'confirm' as const,
234
+ title: resolve(key, options.titleVars) ?? key,
235
+ body: resolve(options.body, options.bodyVars),
236
+ key,
237
+ options,
238
+ };
239
+
240
+ open(dialog, (answer) => done(answer === true || answer === confirm));
241
+ }),
242
+ alert: (key, options = {}) =>
243
+ new Promise((done) => {
244
+ const dialog = {
245
+ kind: 'alert' as const,
246
+ title: resolve(key, options.titleVars) ?? key,
247
+ body: resolve(options.body, options.bodyVars),
248
+ key,
249
+ options,
250
+ };
251
+
252
+ open(dialog, () => done());
253
+ }),
254
+ dismissAll,
255
+ start: () => {},
256
+ stop: dismissAll,
257
+ };
258
+ }
@@ -0,0 +1,54 @@
1
+ /** The fake `fs`: an in-memory documents folder. */
2
+ export type FakeFs = {
3
+ /** Every file, by absolute path, with the content as written; a test may read or add to it. */
4
+ files: Map<string, string>;
5
+ documentsPath: string;
6
+ exists: (path: string, inDocuments?: boolean) => Promise<boolean>;
7
+ readFile: (path: string, encoding?: string, inDocuments?: boolean) => Promise<string>;
8
+ writeFile: (path: string, content: string, encoding?: string) => Promise<string>;
9
+ removeFile: (path: string) => Promise<boolean>;
10
+ };
11
+
12
+ const DOCUMENTS = '/documents';
13
+
14
+ /**
15
+ * @description Creates the fake `fs`, which keeps files in memory and resolves relative paths against its documents folder, as the client does.
16
+ * @returns The fs.
17
+ */
18
+ export function createFakeFs(): FakeFs {
19
+ const files = new Map<string, string>();
20
+
21
+ function resolve(path: string, inDocuments = true) {
22
+ return inDocuments ? `${DOCUMENTS}/${path}` : path;
23
+ }
24
+
25
+ return {
26
+ files,
27
+ documentsPath: DOCUMENTS,
28
+ exists: async (path, inDocuments) => files.has(resolve(path, inDocuments)),
29
+ async readFile(path, _encoding, inDocuments) {
30
+ const content = files.get(resolve(path, inDocuments));
31
+
32
+ if (content === undefined) {
33
+ throw new Error(`Failed to read file ${path}: it does not exist.`);
34
+ }
35
+
36
+ return content;
37
+ },
38
+ async writeFile(path, content) {
39
+ files.set(resolve(path), content);
40
+
41
+ return resolve(path);
42
+ },
43
+ async removeFile(path) {
44
+ const target = resolve(path);
45
+ const removed = [...files.keys()].filter(
46
+ (file) => file === target || file.startsWith(`${target}/`),
47
+ );
48
+
49
+ for (const file of removed) files.delete(file);
50
+
51
+ return removed.length > 0;
52
+ },
53
+ };
54
+ }