@flighthq/scene2d-formats 0.3.0 → 0.3.1-next.320.fe56fc2

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.
@@ -1,5 +1,6 @@
1
+ import { collectImportDiagnostics } from '@flighthq/importdiagnostics/contract';
1
2
  import type { RiveArtboardGraph, RiveCoreObject } from '@flighthq/types/contract';
2
- import { PathCommand, RiveFieldType } from '@flighthq/types/contract';
3
+ import { ImportDiagnosticSeverity, PathCommand, RiveFieldType } from '@flighthq/types/contract';
3
4
 
4
5
  import { createRivePath } from './riveShapePath';
5
6
 
@@ -18,12 +19,51 @@ const STRAIGHT_VERTEX = 5;
18
19
  const CUBIC_DETACHED_VERTEX = 6;
19
20
  const CUBIC_MIRRORED_VERTEX = 35;
20
21
  const CUBIC_ASYMMETRIC_VERTEX = 34;
22
+ const PARAMETRIC_PATH = 15;
21
23
 
22
24
  describe('createRivePath', () => {
23
25
  it('returns null for a component that is not a path', () => {
24
26
  expect(createRivePath(object(SHAPE, {}), artboardOf([object(SHAPE, {})]), 0)).toBeNull();
25
27
  });
26
28
 
29
+ it('reports a path kind it does not build, which an empty result cannot be told apart from', () => {
30
+ // A caller sees the same empty result whether the kind was unsupported or a points path legitimately
31
+ // stated no vertices. Only one of those lost something.
32
+ const diagnostics = collectImportDiagnostics((sink) => {
33
+ expect(createRivePath(object(SHAPE, {}), artboardOf([object(SHAPE, {})]), 0, sink)).toBeNull();
34
+ });
35
+
36
+ expect(diagnostics).toHaveLength(1);
37
+ expect(diagnostics[0].kind).toBe('rive.path-kind-unsupported');
38
+ expect(diagnostics[0].severity).toBe(ImportDiagnosticSeverity.Drop);
39
+ expect(diagnostics[0].detail).toEqual({ typeKey: SHAPE });
40
+ });
41
+
42
+ it('reports an unrecognised parametric kind, which is drawn as a rectangle rather than dropped', () => {
43
+ // Reaching this needs a parametric type none of the four handled cases covers. `ParametricPath`
44
+ // itself is the only one in today's table; the case that matters in the field is a NEWER Rive shape
45
+ // kind this importer has not learned, which lands here identically.
46
+ let path: ReturnType<typeof createRivePath> = null;
47
+ const diagnostics = collectImportDiagnostics((sink) => {
48
+ const source = object(PARAMETRIC_PATH, { 20: 10, 21: 10 });
49
+ path = createRivePath(source, artboardOf([source]), 0, sink);
50
+ });
51
+
52
+ expect(diagnostics).toHaveLength(1);
53
+ expect(diagnostics[0].kind).toBe('rive.parametric-path-substituted');
54
+ expect(diagnostics[0].severity).toBe(ImportDiagnosticSeverity.Recover);
55
+ expect(diagnostics[0].detail).toEqual({ substitutedAs: 'rectangle', typeKey: PARAMETRIC_PATH });
56
+ // The substitution itself, not merely the report: the geometry really is a closed four-sided box.
57
+ expect(path).not.toBeNull();
58
+ expect(path!.commands).toEqual([
59
+ PathCommand.MOVE_TO,
60
+ PathCommand.LINE_TO,
61
+ PathCommand.LINE_TO,
62
+ PathCommand.LINE_TO,
63
+ PathCommand.CLOSE,
64
+ ]);
65
+ });
66
+
27
67
  it('builds an open polyline from straight vertices', () => {
28
68
  const path = pointsPath(false, [
29
69
  object(STRAIGHT_VERTEX, { 24: 0, 25: 0 }),
@@ -386,6 +386,40 @@ describe('SVG conformance matrix', () => {
386
386
  expect(text.data.textFormatRanges[0].format.color).toBe(0x0000ffff);
387
387
  });
388
388
 
389
+ it('keeps fill-none text transparent while a painted tspan remains visible', () => {
390
+ const root = createScene2DFromSvgDocument('<svg><text fill="none">A<tspan fill="red">B</tspan></text></svg>');
391
+ const text = getNodeChildAt(root, 0) as RichText;
392
+
393
+ expect(text.data.text).toBe('AB');
394
+ expect(text.data.textFormat.color).toBe(0x00000000);
395
+ expect(text.data.textFormatRanges[0].format.color).toBe(0xff0000ff);
396
+ });
397
+
398
+ it('omits hidden tspan content while permitting a visible descendant override', () => {
399
+ const root = createScene2DFromSvgDocument(
400
+ '<svg><text>A<tspan visibility="hidden">B<tspan visibility="visible">C</tspan></tspan>' +
401
+ '<tspan display="none">D<tspan display="inline">E</tspan></tspan>F</text></svg>',
402
+ );
403
+ const text = getNodeChildAt(root, 0) as RichText;
404
+
405
+ expect(text.data.text).toBe('ACF');
406
+ });
407
+
408
+ it('composes nested tspan opacity without applying the text opacity twice', () => {
409
+ const root = createScene2DFromSvgDocument(
410
+ '<svg><text opacity=".8">A<tspan opacity=".5">B<tspan opacity=".5">C</tspan></tspan></text></svg>',
411
+ );
412
+ const text = getNodeChildAt(root, 0) as RichText;
413
+
414
+ expect(text.data.text).toBe('ABC');
415
+ expect(text.alpha).toBeCloseTo(0.8);
416
+ expect(text.data.textFormat.color).toBe(0x000000ff);
417
+ expect(text.data.textFormatRanges).toEqual([
418
+ expect.objectContaining({ end: 2, format: expect.objectContaining({ color: 0x00000080 }), start: 1 }),
419
+ expect.objectContaining({ end: 3, format: expect.objectContaining({ color: 0x00000040 }), start: 2 }),
420
+ ]);
421
+ });
422
+
389
423
  it('suppresses display none while allowing visibility descendants to override', () => {
390
424
  const root = createScene2DFromSvgDocument(`
391
425
  <svg>