@flighthq/text 0.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.
@@ -0,0 +1,121 @@
1
+ import { createDisplayObjectGeneric, createDisplayObjectRuntime, getDisplayObjectRuntime, } from '@flighthq/displayobject';
2
+ import { invalidateNodeLocalBounds, invalidateNodeLocalContent } from '@flighthq/node';
3
+ import { computeTextBoundsRectangle, createTextFormatRange } from '@flighthq/textlayout';
4
+ import { TextLabelKind } from '@flighthq/types';
5
+ import { ensureTextLayout, getTextLayout } from './textLabelLayout';
6
+ // Per-kind layout-params hook for ensureTextLayout: a TextLabel is a single run, so it lays out its
7
+ // whole string with one format range and no wrap — the lean path that skips RichText's range/html
8
+ // assembly and wrap/multiline machinery. Local: only assigned onto the runtime in createTextLabelRuntime.
9
+ function buildTextLabelLayoutParams(source, measure) {
10
+ const data = source.data;
11
+ return {
12
+ formatRanges: [createTextFormatRange(data.textFormat, 0, data.text.length)],
13
+ height: data.height,
14
+ measure,
15
+ text: data.text,
16
+ width: data.width,
17
+ };
18
+ }
19
+ export function appendTextLabelString(source, value) {
20
+ if (value.length === 0)
21
+ return;
22
+ source.data.text += value;
23
+ invalidateNodeLocalContent(source);
24
+ }
25
+ // The field-box bounds in local space, mirroring computeRichTextLocalBoundsRectangle. A fixed field
26
+ // (autoSize 'none') is the user width/height at the origin. Under autoSize, the box is the measured
27
+ // content (textWidth/textHeight + gutter) positioned by the left/right/center anchor. The layout is
28
+ // ensured on demand here, so a TextLabel's autoSize bounds are queryable before it is ever rendered.
29
+ // Falls back to the fixed box until a measure provider is registered.
30
+ export function computeTextLabelLocalBoundsRectangle(out, source) {
31
+ const label = source;
32
+ const data = label.data;
33
+ if (data.autoSize === 'none') {
34
+ out.x = 0;
35
+ out.y = 0;
36
+ out.width = data.width;
37
+ out.height = data.height;
38
+ return;
39
+ }
40
+ ensureTextLayout(label);
41
+ const layout = getTextLayout(label);
42
+ if (layout === null) {
43
+ out.x = 0;
44
+ out.y = 0;
45
+ out.width = data.width;
46
+ out.height = data.height;
47
+ return;
48
+ }
49
+ computeTextBoundsRectangle(out, data, layout);
50
+ }
51
+ export function createTextLabel(obj) {
52
+ return createDisplayObjectGeneric(TextLabelKind, obj, createTextLabelData, createTextLabelRuntime);
53
+ }
54
+ export function createTextLabelData(data) {
55
+ return {
56
+ autoSize: data?.autoSize ?? 'none',
57
+ height: data?.height ?? 100,
58
+ text: data?.text ?? '',
59
+ textFormat: data?.textFormat ?? {},
60
+ width: data?.width ?? 100,
61
+ };
62
+ }
63
+ export function createTextLabelRuntime() {
64
+ const out = createDisplayObjectRuntime(defaultMethods);
65
+ // buildTextLayoutParams is a text-specific per-kind seam, not a display-object trait, so no
66
+ // trait-init copies it off defaultMethods — assign it onto the runtime directly.
67
+ out.buildTextLayoutParams = buildTextLabelLayoutParams;
68
+ out.textLayout = null;
69
+ out.textLayoutUsingContentId = -1;
70
+ return out;
71
+ }
72
+ export function getTextLabelFormat(source) {
73
+ return source.data.textFormat;
74
+ }
75
+ export function getTextLabelRuntime(source) {
76
+ return getDisplayObjectRuntime(source);
77
+ }
78
+ export function getTextLabelString(source) {
79
+ return source.data.text;
80
+ }
81
+ export function setTextLabelAutoSize(source, value) {
82
+ const data = source.data;
83
+ if (data.autoSize === value)
84
+ return;
85
+ data.autoSize = value;
86
+ invalidateNodeLocalContent(source);
87
+ invalidateNodeLocalBounds(source);
88
+ }
89
+ // Replaces the format wholesale. textFormat is an object reference, so callers pass a new format to
90
+ // apply; the content revision bumps unconditionally because field-level equality is not tracked.
91
+ export function setTextLabelFormat(source, value) {
92
+ source.data.textFormat = value;
93
+ invalidateNodeLocalContent(source);
94
+ }
95
+ export function setTextLabelHeight(source, value) {
96
+ const data = source.data;
97
+ if (data.height === value)
98
+ return;
99
+ data.height = value;
100
+ invalidateNodeLocalContent(source);
101
+ invalidateNodeLocalBounds(source);
102
+ }
103
+ export function setTextLabelString(source, value) {
104
+ const data = source.data;
105
+ if (data.text === value)
106
+ return;
107
+ data.text = value;
108
+ invalidateNodeLocalContent(source);
109
+ }
110
+ export function setTextLabelWidth(source, value) {
111
+ const data = source.data;
112
+ if (data.width === value)
113
+ return;
114
+ data.width = value;
115
+ invalidateNodeLocalContent(source);
116
+ invalidateNodeLocalBounds(source);
117
+ }
118
+ const defaultMethods = {
119
+ computeLocalBoundsRectangle: computeTextLabelLocalBoundsRectangle,
120
+ };
121
+ //# sourceMappingURL=textLabel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textLabel.js","sourceRoot":"","sources":["../src/textLabel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { TextLabel, TextLayoutResult, TextMetrics } from '@flighthq/types';
2
+ export declare function ensureTextLayout(source: Readonly<TextLabel>): void;
3
+ export declare function getTextLayout(source: Readonly<TextLabel>): TextLayoutResult | null;
4
+ export declare function getTextLayoutMetrics(out: TextMetrics, source: Readonly<TextLabel>): void;
5
+ //# sourceMappingURL=textLabelLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textLabelLayout.d.ts","sourceRoot":"","sources":["../src/textLabelLayout.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAoB,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAWlG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,IAAI,CAYlE;AAID,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAGlF;AAID,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,IAAI,CASxF"}
@@ -0,0 +1,44 @@
1
+ import { getDisplayObjectRuntime } from '@flighthq/displayobject';
2
+ import { getNodeLocalContentRevision } from '@flighthq/node';
3
+ import { computeTextLayout, getTextLayoutMeasureProvider, getTextLayoutResult, getTextMetrics, } from '@flighthq/textlayout';
4
+ // Lazily refreshes the cached text layout for `source` — TextLabel or RichText alike —
5
+ // mirroring the node graph's ensure* pattern (e.g. ensureNodeLocalBoundsRectangle). If the
6
+ // local-content revision changed since the layout was last computed, it asks the node's kind to
7
+ // assemble its layout params (the single per-kind difference) via runtime.buildTextLayoutParams,
8
+ // recomputes, and restamps. Cheap and idempotent when already current. Callable from anywhere needing
9
+ // text metrics, bounds, or rendering — no render pass required, which is what lets text be measured
10
+ // before (or without) it is ever drawn. If no measure provider is registered yet, the layout is left
11
+ // as-is (null or stale) until setTextLayoutMeasureProvider is called, after which the next ensure
12
+ // refreshes it.
13
+ export function ensureTextLayout(source) {
14
+ const runtime = getDisplayObjectRuntime(source);
15
+ const contentId = getNodeLocalContentRevision(source);
16
+ if (runtime.textLayout !== null && runtime.textLayoutUsingContentId === contentId)
17
+ return;
18
+ const measure = getTextLayoutMeasureProvider();
19
+ if (measure === null)
20
+ return;
21
+ const params = runtime.buildTextLayoutParams(source, measure);
22
+ const result = getTextLayoutResult(runtime);
23
+ computeTextLayout(result, params);
24
+ runtime.textLayoutUsingContentId = contentId;
25
+ }
26
+ // Ensures the layout is current and returns it, or null if no measure provider has been registered
27
+ // yet (so a caller can distinguish "no text" from "not yet measurable").
28
+ export function getTextLayout(source) {
29
+ ensureTextLayout(source);
30
+ return getDisplayObjectRuntime(source).textLayout;
31
+ }
32
+ // Ensures the layout is current and fills `out` with the measured content size (Flash
33
+ // textWidth/textHeight). Zeroes `out` when no layout is available (no measure provider registered).
34
+ export function getTextLayoutMetrics(out, source) {
35
+ const layout = getTextLayout(source);
36
+ if (layout === null) {
37
+ out.height = 0;
38
+ out.numLines = 0;
39
+ out.width = 0;
40
+ return;
41
+ }
42
+ getTextMetrics(out, layout);
43
+ }
44
+ //# sourceMappingURL=textLabelLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textLabelLayout.js","sourceRoot":"","sources":["../src/textLabelLayout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,mBAAmB,EACnB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAG9B,uFAAuF;AACvF,2FAA2F;AAC3F,gGAAgG;AAChG,iGAAiG;AACjG,sGAAsG;AACtG,oGAAoG;AACpG,qGAAqG;AACrG,kGAAkG;AAClG,gBAAgB;AAChB,MAAM,UAAU,gBAAgB,CAAC,MAA2B;IAC1D,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,CAAqB,CAAC;IACpE,MAAM,SAAS,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,wBAAwB,KAAK,SAAS;QAAE,OAAO;IAE1F,MAAM,OAAO,GAAG,4BAA4B,EAAE,CAAC;IAC/C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC5C,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC;AAC/C,CAAC;AAED,mGAAmG;AACnG,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,MAA2B;IACvD,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,OAAQ,uBAAuB,CAAC,MAAM,CAAsB,CAAC,UAAU,CAAC;AAC1E,CAAC;AAED,sFAAsF;AACtF,oGAAoG;AACpG,MAAM,UAAU,oBAAoB,CAAC,GAAgB,EAAE,MAA2B;IAChF,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACf,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;QACjB,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;QACd,OAAO;IACT,CAAC;IACD,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAC9B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@flighthq/text",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src/**/*.test.ts",
16
+ "!dist/**/*.test.js",
17
+ "!dist/**/*.test.d.ts",
18
+ "!dist/**/*.test.js.map",
19
+ "!dist/**/*.test.d.ts.map"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -b",
23
+ "clean": "tsc -b --clean",
24
+ "test": "vitest run --config vitest.config.ts",
25
+ "test:watch": "vitest --watch --config vitest.config.ts",
26
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
27
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
28
+ },
29
+ "dependencies": {
30
+ "@flighthq/displayobject": "0.1.0",
31
+ "@flighthq/entity": "0.1.0",
32
+ "@flighthq/geometry": "0.1.0",
33
+ "@flighthq/node": "0.1.0",
34
+ "@flighthq/signals": "0.1.0",
35
+ "@flighthq/textlayout": "0.1.0",
36
+ "@flighthq/types": "0.1.0"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "^5.3.0"
40
+ },
41
+ "description": "Text display objects: single-format TextLabel and multi-format RichText built on the text-layout spine",
42
+ "sideEffects": false
43
+ }
@@ -0,0 +1,256 @@
1
+ import { createRectangle } from '@flighthq/geometry';
2
+ import { getNodeLocalBoundsRevision, getNodeLocalContentRevision } from '@flighthq/node';
3
+ import type { NativeText, NativeTextRuntime, Node, PartialNode } from '@flighthq/types';
4
+ import { NativeTextKind } from '@flighthq/types';
5
+
6
+ import {
7
+ computeNativeTextLocalBoundsRectangle,
8
+ createNativeText,
9
+ createNativeTextData,
10
+ createNativeTextRuntime,
11
+ getNativeTextMeasuredHeight,
12
+ getNativeTextMeasuredWidth,
13
+ getNativeTextRuntime,
14
+ getNativeTextString,
15
+ getNativeTextStyle,
16
+ patchNativeTextStyle,
17
+ setNativeTextAutoSize,
18
+ setNativeTextHeight,
19
+ setNativeTextString,
20
+ setNativeTextStyle,
21
+ setNativeTextWidth,
22
+ } from './nativeText';
23
+
24
+ describe('computeNativeTextLocalBoundsRectangle', () => {
25
+ it('sets out dimensions from data width and height when autoSize is none', () => {
26
+ const native = createNativeText({ data: { width: 200, height: 50 } });
27
+ const out = createRectangle();
28
+ computeNativeTextLocalBoundsRectangle(out, native as unknown as Node);
29
+ expect(out.x).toBe(0);
30
+ expect(out.width).toBe(200);
31
+ expect(out.height).toBe(50);
32
+ });
33
+
34
+ it('falls back to the fixed box under autoSize until the element has been measured', () => {
35
+ const native = createNativeText({ data: { autoSize: 'left', width: 200, height: 50 } });
36
+ const out = createRectangle();
37
+ computeNativeTextLocalBoundsRectangle(out, native as unknown as Node);
38
+ expect(out.width).toBe(200);
39
+ expect(out.height).toBe(50);
40
+ });
41
+
42
+ it('uses the measured size the platform renderer wrote back under autoSize', () => {
43
+ const native = createNativeText({ data: { autoSize: 'left', width: 200, height: 50 } });
44
+ const runtime = getNativeTextRuntime(native) as NativeTextRuntime;
45
+ runtime.measuredWidth = 80;
46
+ runtime.measuredHeight = 24;
47
+ const out = createRectangle();
48
+ computeNativeTextLocalBoundsRectangle(out, native as unknown as Node);
49
+ expect(out.width).toBe(80);
50
+ expect(out.height).toBe(24);
51
+ });
52
+ });
53
+
54
+ describe('createNativeText', () => {
55
+ let native: NativeText;
56
+
57
+ beforeEach(() => {
58
+ native = createNativeText();
59
+ });
60
+
61
+ it('initializes default values', () => {
62
+ expect(native.data.text).toBe('');
63
+ expect(native.data.autoSize).toBe('none');
64
+ expect(native.data.width).toBe(100);
65
+ expect(native.data.height).toBe(100);
66
+ expect(native.data.style).not.toBeNull();
67
+ expect(native.kind).toStrictEqual(NativeTextKind);
68
+ });
69
+
70
+ it('allows pre-defined values', () => {
71
+ const base: PartialNode<NativeText> = {
72
+ data: {
73
+ autoSize: 'left',
74
+ text: 'foo',
75
+ width: 300,
76
+ height: 40,
77
+ },
78
+ };
79
+ const obj = createNativeText(base);
80
+ expect(obj.data.autoSize).toStrictEqual(base.data!.autoSize);
81
+ expect(obj.data.text).toStrictEqual(base.data!.text);
82
+ expect(obj.data.width).toBe(300);
83
+ expect(obj.data.height).toBe(40);
84
+ });
85
+ });
86
+
87
+ describe('createNativeTextData', () => {
88
+ it('returns default values', () => {
89
+ const data = createNativeTextData();
90
+ expect(data.autoSize).toBe('none');
91
+ expect(data.height).toBe(100);
92
+ expect(data.text).toBe('');
93
+ expect(data.style).not.toBeNull();
94
+ expect(data.width).toBe(100);
95
+ });
96
+
97
+ it('allows pre-defined values', () => {
98
+ const data = createNativeTextData({ autoSize: 'left', text: 'hello', width: 250, height: 30 });
99
+ expect(data.autoSize).toBe('left');
100
+ expect(data.text).toBe('hello');
101
+ expect(data.width).toBe(250);
102
+ expect(data.height).toBe(30);
103
+ });
104
+ });
105
+
106
+ describe('createNativeTextRuntime', () => {
107
+ it('returns a non-null runtime with empty platform slots', () => {
108
+ const runtime = createNativeTextRuntime();
109
+ expect(runtime).not.toBeNull();
110
+ expect(runtime.element).toBeNull();
111
+ expect(runtime.measuredWidth).toBe(0);
112
+ expect(runtime.measuredHeight).toBe(0);
113
+ });
114
+
115
+ it('uses computeNativeTextLocalBoundsRectangle', () => {
116
+ const runtime = createNativeTextRuntime();
117
+ expect(runtime.computeLocalBoundsRectangle).toStrictEqual(computeNativeTextLocalBoundsRectangle);
118
+ });
119
+ });
120
+
121
+ describe('getNativeTextMeasuredHeight', () => {
122
+ it('returns 0 before the platform renderer writes a measurement', () => {
123
+ const native = createNativeText();
124
+ expect(getNativeTextMeasuredHeight(native)).toBe(0);
125
+ });
126
+
127
+ it('returns the value the renderer wrote back', () => {
128
+ const native = createNativeText();
129
+ (getNativeTextRuntime(native) as NativeTextRuntime).measuredHeight = 24;
130
+ expect(getNativeTextMeasuredHeight(native)).toBe(24);
131
+ });
132
+ });
133
+
134
+ describe('getNativeTextMeasuredWidth', () => {
135
+ it('returns 0 before the platform renderer writes a measurement', () => {
136
+ const native = createNativeText();
137
+ expect(getNativeTextMeasuredWidth(native)).toBe(0);
138
+ });
139
+
140
+ it('returns the value the renderer wrote back', () => {
141
+ const native = createNativeText();
142
+ (getNativeTextRuntime(native) as NativeTextRuntime).measuredWidth = 80;
143
+ expect(getNativeTextMeasuredWidth(native)).toBe(80);
144
+ });
145
+ });
146
+
147
+ describe('getNativeTextRuntime', () => {
148
+ it('returns the runtime for a NativeText', () => {
149
+ const native = createNativeText();
150
+ const runtime = getNativeTextRuntime(native);
151
+ expect(runtime).not.toBeNull();
152
+ });
153
+ });
154
+
155
+ describe('getNativeTextString', () => {
156
+ it('returns the text field', () => {
157
+ const native = createNativeText({ data: { text: 'hello' } });
158
+ expect(getNativeTextString(native)).toBe('hello');
159
+ });
160
+ });
161
+
162
+ describe('getNativeTextStyle', () => {
163
+ it('returns the style field', () => {
164
+ const style = { size: 16, bold: true };
165
+ const native = createNativeText({ data: { style } });
166
+ expect(getNativeTextStyle(native)).toBe(native.data.style);
167
+ });
168
+ });
169
+
170
+ describe('patchNativeTextStyle', () => {
171
+ it('merges the patch into the existing style', () => {
172
+ const native = createNativeText({ data: { style: { size: 14, bold: false } } });
173
+ patchNativeTextStyle(native, { bold: true, color: 0xff0000 });
174
+ expect(native.data.style.size).toBe(14);
175
+ expect(native.data.style.bold).toBe(true);
176
+ expect(native.data.style.color).toBe(0xff0000);
177
+ });
178
+
179
+ it('bumps content after patching', () => {
180
+ const native = createNativeText();
181
+ const content = getNodeLocalContentRevision(native);
182
+ patchNativeTextStyle(native, { size: 20 });
183
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
184
+ });
185
+ });
186
+
187
+ describe('setNativeTextAutoSize', () => {
188
+ it('sets autoSize, bumps content, and invalidates local bounds', () => {
189
+ const native = createNativeText();
190
+ const content = getNodeLocalContentRevision(native);
191
+ const bounds = getNodeLocalBoundsRevision(native);
192
+ setNativeTextAutoSize(native, 'left');
193
+ expect(native.data.autoSize).toBe('left');
194
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
195
+ expect(getNodeLocalBoundsRevision(native)).not.toBe(bounds);
196
+ });
197
+
198
+ it('does not bump content when the value is unchanged', () => {
199
+ const native = createNativeText();
200
+ const content = getNodeLocalContentRevision(native);
201
+ setNativeTextAutoSize(native, 'none');
202
+ expect(getNodeLocalContentRevision(native)).toBe(content);
203
+ });
204
+ });
205
+
206
+ describe('setNativeTextHeight', () => {
207
+ it('sets height, bumps content, and invalidates local bounds', () => {
208
+ const native = createNativeText();
209
+ const content = getNodeLocalContentRevision(native);
210
+ const bounds = getNodeLocalBoundsRevision(native);
211
+ setNativeTextHeight(native, 250);
212
+ expect(native.data.height).toBe(250);
213
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
214
+ expect(getNodeLocalBoundsRevision(native)).not.toBe(bounds);
215
+ });
216
+ });
217
+
218
+ describe('setNativeTextString', () => {
219
+ it('sets text and bumps content', () => {
220
+ const native = createNativeText();
221
+ const content = getNodeLocalContentRevision(native);
222
+ setNativeTextString(native, 'hello');
223
+ expect(native.data.text).toBe('hello');
224
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
225
+ });
226
+
227
+ it('does not bump content when the value is unchanged', () => {
228
+ const native = createNativeText({ data: { text: 'same' } });
229
+ const content = getNodeLocalContentRevision(native);
230
+ setNativeTextString(native, 'same');
231
+ expect(getNodeLocalContentRevision(native)).toBe(content);
232
+ });
233
+ });
234
+
235
+ describe('setNativeTextStyle', () => {
236
+ it('replaces the style and bumps content', () => {
237
+ const native = createNativeText();
238
+ const content = getNodeLocalContentRevision(native);
239
+ const style = { size: 24 };
240
+ setNativeTextStyle(native, style);
241
+ expect(native.data.style).toBe(style);
242
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
243
+ });
244
+ });
245
+
246
+ describe('setNativeTextWidth', () => {
247
+ it('sets width, bumps content, and invalidates local bounds', () => {
248
+ const native = createNativeText();
249
+ const content = getNodeLocalContentRevision(native);
250
+ const bounds = getNodeLocalBoundsRevision(native);
251
+ setNativeTextWidth(native, 300);
252
+ expect(native.data.width).toBe(300);
253
+ expect(getNodeLocalContentRevision(native)).toBe(content + 1);
254
+ expect(getNodeLocalBoundsRevision(native)).not.toBe(bounds);
255
+ });
256
+ });