js-draw 0.1.0 → 0.1.3
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 +12 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.js +6 -3
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EditorImage.js +6 -3
- package/dist/src/Pointer.d.ts +3 -2
- package/dist/src/Pointer.js +12 -3
- package/dist/src/SVGLoader.d.ts +11 -0
- package/dist/src/SVGLoader.js +113 -4
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/Viewport.js +12 -2
- package/dist/src/components/AbstractComponent.d.ts +6 -0
- package/dist/src/components/AbstractComponent.js +11 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/Text.d.ts +30 -0
- package/dist/src/components/Text.js +109 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +1 -1
- package/dist/src/components/localization.d.ts +1 -0
- package/dist/src/components/localization.js +1 -0
- package/dist/src/geometry/Mat33.d.ts +1 -0
- package/dist/src/geometry/Mat33.js +30 -0
- package/dist/src/geometry/Path.js +105 -67
- package/dist/src/geometry/Rect2.d.ts +2 -0
- package/dist/src/geometry/Rect2.js +25 -8
- package/dist/src/rendering/Display.js +4 -3
- package/dist/src/rendering/caching/CacheRecord.js +2 -1
- package/dist/src/rendering/caching/CacheRecordManager.js +2 -10
- package/dist/src/rendering/caching/RenderingCache.js +10 -4
- package/dist/src/rendering/caching/RenderingCacheNode.js +10 -3
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +1 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +13 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +6 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +50 -7
- package/dist/src/testing/loadExpectExtensions.js +1 -4
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +216 -154
- package/dist/src/toolbar/icons.d.ts +12 -0
- package/dist/src/toolbar/icons.js +197 -0
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/types.d.ts +4 -0
- package/dist/src/tools/PanZoom.d.ts +9 -6
- package/dist/src/tools/PanZoom.js +30 -21
- package/dist/src/tools/Pen.js +8 -3
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +29 -0
- package/dist/src/tools/TextTool.js +154 -0
- package/dist/src/tools/ToolController.d.ts +5 -5
- package/dist/src/tools/ToolController.js +10 -9
- package/dist/src/tools/localization.d.ts +3 -0
- package/dist/src/tools/localization.js +3 -0
- package/package.json +1 -1
- package/src/Editor.ts +7 -3
- package/src/EditorImage.ts +7 -3
- package/src/Pointer.ts +13 -4
- package/src/SVGLoader.ts +146 -5
- package/src/Viewport.ts +15 -3
- package/src/components/AbstractComponent.ts +16 -1
- package/src/components/SVGGlobalAttributesObject.ts +0 -1
- package/src/components/Stroke.ts +1 -1
- package/src/components/Text.ts +136 -0
- package/src/components/builders/FreehandLineBuilder.ts +1 -1
- package/src/components/localization.ts +2 -0
- package/src/geometry/Mat33.test.ts +44 -0
- package/src/geometry/Mat33.ts +41 -0
- package/src/geometry/Path.fromString.test.ts +94 -4
- package/src/geometry/Path.toString.test.ts +7 -3
- package/src/geometry/Path.ts +110 -68
- package/src/geometry/Rect2.test.ts +9 -0
- package/src/geometry/Rect2.ts +33 -8
- package/src/rendering/Display.ts +4 -3
- package/src/rendering/caching/CacheRecord.ts +2 -1
- package/src/rendering/caching/CacheRecordManager.ts +2 -12
- package/src/rendering/caching/RenderingCache.test.ts +1 -1
- package/src/rendering/caching/RenderingCache.ts +11 -4
- package/src/rendering/caching/RenderingCacheNode.ts +16 -3
- package/src/rendering/caching/testUtils.ts +1 -0
- package/src/rendering/caching/types.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +18 -1
- package/src/rendering/renderers/CanvasRenderer.ts +34 -10
- package/src/rendering/renderers/DummyRenderer.ts +8 -0
- package/src/rendering/renderers/SVGRenderer.ts +57 -10
- package/src/testing/loadExpectExtensions.ts +1 -4
- package/src/toolbar/HTMLToolbar.ts +262 -170
- package/src/toolbar/icons.ts +226 -0
- package/src/toolbar/localization.ts +9 -2
- package/src/toolbar/toolbar.css +21 -8
- package/src/toolbar/types.ts +5 -0
- package/src/tools/PanZoom.ts +37 -27
- package/src/tools/Pen.ts +7 -3
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +206 -0
- package/src/tools/ToolController.ts +7 -5
- package/src/tools/localization.ts +7 -0
@@ -172,7 +172,12 @@ export default class RenderingCacheNode {
|
|
172
172
|
const newItems = [];
|
173
173
|
// Divide [items] until nodes are leaves or smaller than this
|
174
174
|
for (const item of items) {
|
175
|
-
|
175
|
+
const bbox = item.getBBox();
|
176
|
+
if (!bbox.intersects(this.region)) {
|
177
|
+
continue;
|
178
|
+
}
|
179
|
+
|
180
|
+
if (bbox.maxDimension >= this.region.maxDimension) {
|
176
181
|
newItems.push(...item.getChildrenOrSelfIntersectingRegion(this.region));
|
177
182
|
} else {
|
178
183
|
newItems.push(item);
|
@@ -186,6 +191,10 @@ export default class RenderingCacheNode {
|
|
186
191
|
return;
|
187
192
|
}
|
188
193
|
|
194
|
+
if (debugMode) {
|
195
|
+
screenRenderer.drawRect(this.region, 0.5 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow });
|
196
|
+
}
|
197
|
+
|
189
198
|
// Could we render direclty from [this] or do we need to recurse?
|
190
199
|
const couldRender = this.renderingWouldBeHighEnoughResolution(viewport);
|
191
200
|
if (!couldRender) {
|
@@ -198,7 +207,11 @@ export default class RenderingCacheNode {
|
|
198
207
|
// Determine whether we already have rendered the items
|
199
208
|
const leaves = [];
|
200
209
|
for (const item of items) {
|
201
|
-
leaves.push(
|
210
|
+
leaves.push(
|
211
|
+
...item.getLeavesIntersectingRegion(
|
212
|
+
this.region, rect => rect.w / this.region.w < 2 / this.cacheState.props.blockResolution.x,
|
213
|
+
)
|
214
|
+
);
|
202
215
|
}
|
203
216
|
sortLeavesByZIndex(leaves);
|
204
217
|
const leavesByIds = this.computeSortedByLeafIds(leaves);
|
@@ -266,7 +279,7 @@ export default class RenderingCacheNode {
|
|
266
279
|
}
|
267
280
|
|
268
281
|
if (debugMode) {
|
269
|
-
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.
|
282
|
+
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
|
270
283
|
}
|
271
284
|
}
|
272
285
|
}
|
@@ -21,6 +21,10 @@ export interface CacheProps {
|
|
21
21
|
|
22
22
|
// Minimum component count to cache, rather than just re-render each time.
|
23
23
|
minComponentsPerCache: number;
|
24
|
+
|
25
|
+
// Minimum number of strokes/etc. to use the cache to render, isntead of
|
26
|
+
// rendering directly.
|
27
|
+
minComponentsToUseCache: number;
|
24
28
|
}
|
25
29
|
|
26
30
|
// CacheRecordManager relies on a partial copy of the shared state. Thus,
|
@@ -1,4 +1,6 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
+
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
|
+
import { TextStyle } from '../../components/Text';
|
2
4
|
import Mat33 from '../../geometry/Mat33';
|
3
5
|
import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
|
4
6
|
import Rect2 from '../../geometry/Rect2';
|
@@ -28,6 +30,7 @@ const stylesEqual = (a: RenderingStyle, b: RenderingStyle) => {
|
|
28
30
|
export default abstract class AbstractRenderer {
|
29
31
|
// If null, this' transformation is linked to the Viewport
|
30
32
|
private selfTransform: Mat33|null = null;
|
33
|
+
private transformStack: Array<Mat33|null> = [];
|
31
34
|
|
32
35
|
protected constructor(private viewport: Viewport) { }
|
33
36
|
|
@@ -50,6 +53,7 @@ export default abstract class AbstractRenderer {
|
|
50
53
|
protected abstract traceQuadraticBezierCurve(
|
51
54
|
controlPoint: Point2, endPoint: Point2,
|
52
55
|
): void;
|
56
|
+
public abstract drawText(text: string, transform: Mat33, style: TextStyle): void;
|
53
57
|
|
54
58
|
// Returns true iff the given rectangle is so small, rendering anything within
|
55
59
|
// it has no effect on the image.
|
@@ -128,7 +132,7 @@ export default abstract class AbstractRenderer {
|
|
128
132
|
this.objectLevel ++;
|
129
133
|
}
|
130
134
|
|
131
|
-
public endObject() {
|
135
|
+
public endObject(_loaderData?: LoadSaveDataTable) {
|
132
136
|
// Render the paths all at once
|
133
137
|
this.flushPath();
|
134
138
|
this.currentPaths = null;
|
@@ -165,6 +169,19 @@ export default abstract class AbstractRenderer {
|
|
165
169
|
this.selfTransform = transform;
|
166
170
|
}
|
167
171
|
|
172
|
+
public pushTransform(transform: Mat33) {
|
173
|
+
this.transformStack.push(this.selfTransform);
|
174
|
+
this.setTransform(this.getCanvasToScreenTransform().rightMul(transform));
|
175
|
+
}
|
176
|
+
|
177
|
+
public popTransform() {
|
178
|
+
if (this.transformStack.length === 0) {
|
179
|
+
throw new Error('Unable to pop more transforms than have been pushed!');
|
180
|
+
}
|
181
|
+
|
182
|
+
this.setTransform(this.transformStack.pop() ?? null);
|
183
|
+
}
|
184
|
+
|
168
185
|
// Get the matrix that transforms a vector on the canvas to a vector on this'
|
169
186
|
// rendering target.
|
170
187
|
public getCanvasToScreenTransform(): Mat33 {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
+
import Text, { TextStyle } from '../../components/Text';
|
2
3
|
import Mat33 from '../../geometry/Mat33';
|
3
4
|
import Rect2 from '../../geometry/Rect2';
|
4
5
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
@@ -26,16 +27,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
26
27
|
this.setDraftMode(false);
|
27
28
|
}
|
28
29
|
|
29
|
-
|
30
|
-
return other instanceof CanvasRenderer;
|
31
|
-
}
|
32
|
-
|
33
|
-
public renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void {
|
34
|
-
if (!(other instanceof CanvasRenderer)) {
|
35
|
-
throw new Error(`${other} cannot be rendered onto ${this}`);
|
36
|
-
}
|
37
|
-
transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
|
38
|
-
this.ctx.save();
|
30
|
+
private transformBy(transformBy: Mat33) {
|
39
31
|
// From MDN, transform(a,b,c,d,e,f)
|
40
32
|
// takes input such that
|
41
33
|
// ⎡ a c e ⎤
|
@@ -46,6 +38,19 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
46
38
|
transformBy.a2, transformBy.b2, // c, d
|
47
39
|
transformBy.a3, transformBy.b3, // e, f
|
48
40
|
);
|
41
|
+
}
|
42
|
+
|
43
|
+
public canRenderFromWithoutDataLoss(other: AbstractRenderer) {
|
44
|
+
return other instanceof CanvasRenderer;
|
45
|
+
}
|
46
|
+
|
47
|
+
public renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void {
|
48
|
+
if (!(other instanceof CanvasRenderer)) {
|
49
|
+
throw new Error(`${other} cannot be rendered onto ${this}`);
|
50
|
+
}
|
51
|
+
transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
|
52
|
+
this.ctx.save();
|
53
|
+
this.transformBy(transformBy);
|
49
54
|
this.ctx.drawImage(other.ctx.canvas, 0, 0);
|
50
55
|
this.ctx.restore();
|
51
56
|
}
|
@@ -143,6 +148,25 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
143
148
|
super.drawPath(path);
|
144
149
|
}
|
145
150
|
|
151
|
+
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
152
|
+
this.ctx.save();
|
153
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
154
|
+
this.transformBy(transform);
|
155
|
+
Text.applyTextStyles(this.ctx, style);
|
156
|
+
|
157
|
+
if (style.renderingStyle.fill.a !== 0) {
|
158
|
+
this.ctx.fillStyle = style.renderingStyle.fill.toHexString();
|
159
|
+
this.ctx.fillText(text, 0, 0);
|
160
|
+
}
|
161
|
+
if (style.renderingStyle.stroke) {
|
162
|
+
this.ctx.strokeStyle = style.renderingStyle.stroke.color.toHexString();
|
163
|
+
this.ctx.lineWidth = style.renderingStyle.stroke.width;
|
164
|
+
this.ctx.strokeText(text, 0, 0);
|
165
|
+
}
|
166
|
+
|
167
|
+
this.ctx.restore();
|
168
|
+
}
|
169
|
+
|
146
170
|
private clipLevels: number[] = [];
|
147
171
|
public startObject(boundingBox: Rect2, clip: boolean) {
|
148
172
|
if (this.isTooSmallToRender(boundingBox)) {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
// Renderer that outputs nothing. Useful for automated tests.
|
2
2
|
|
3
|
+
import { TextStyle } from '../../components/Text';
|
3
4
|
import Mat33 from '../../geometry/Mat33';
|
4
5
|
import Rect2 from '../../geometry/Rect2';
|
5
6
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
@@ -14,6 +15,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
14
15
|
public lastFillStyle: RenderingStyle|null = null;
|
15
16
|
public lastPoint: Point2|null = null;
|
16
17
|
public objectNestingLevel: number = 0;
|
18
|
+
public lastText: string|null = null;
|
17
19
|
|
18
20
|
// List of points drawn since the last clear.
|
19
21
|
public pointBuffer: Point2[] = [];
|
@@ -40,6 +42,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
40
42
|
this.clearedCount ++;
|
41
43
|
this.renderedPathCount = 0;
|
42
44
|
this.pointBuffer = [];
|
45
|
+
this.lastText = null;
|
43
46
|
|
44
47
|
// Ensure all objects finished rendering
|
45
48
|
if (this.objectNestingLevel > 0) {
|
@@ -88,6 +91,11 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
88
91
|
// As such, it is unlikely to be the target of automated tests.
|
89
92
|
}
|
90
93
|
|
94
|
+
|
95
|
+
public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
|
96
|
+
this.lastText = text;
|
97
|
+
}
|
98
|
+
|
91
99
|
public startObject(boundingBox: Rect2, _clip: boolean) {
|
92
100
|
super.startObject(boundingBox);
|
93
101
|
|
@@ -1,7 +1,11 @@
|
|
1
1
|
|
2
|
+
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
|
+
import { TextStyle } from '../../components/Text';
|
4
|
+
import Mat33 from '../../geometry/Mat33';
|
2
5
|
import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
|
3
6
|
import Rect2 from '../../geometry/Rect2';
|
4
7
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
8
|
+
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
|
5
9
|
import Viewport from '../../Viewport';
|
6
10
|
import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
|
7
11
|
|
@@ -13,8 +17,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
13
17
|
private lastPathStyle: RenderingStyle|null;
|
14
18
|
private lastPath: PathCommand[]|null;
|
15
19
|
private lastPathStart: Point2|null;
|
20
|
+
private objectElems: SVGElement[]|null = null;
|
16
21
|
|
17
|
-
private mainGroup: SVGGElement;
|
18
22
|
private overwrittenAttrs: Record<string, string|null> = {};
|
19
23
|
|
20
24
|
public constructor(private elem: SVGSVGElement, viewport: Viewport) {
|
@@ -41,8 +45,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
41
45
|
}
|
42
46
|
|
43
47
|
public clear() {
|
44
|
-
this.mainGroup = document.createElementNS(svgNameSpace, 'g');
|
45
|
-
|
46
48
|
// Restore all alltributes
|
47
49
|
for (const attrName in this.overwrittenAttrs) {
|
48
50
|
const value = this.overwrittenAttrs[attrName];
|
@@ -54,9 +56,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
54
56
|
}
|
55
57
|
}
|
56
58
|
this.overwrittenAttrs = {};
|
57
|
-
|
58
|
-
// Remove all children
|
59
|
-
this.elem.replaceChildren(this.mainGroup);
|
60
59
|
}
|
61
60
|
|
62
61
|
protected beginPath(startPoint: Point2) {
|
@@ -106,7 +105,34 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
106
105
|
pathElem.setAttribute('stroke-width', style.stroke.width.toString());
|
107
106
|
}
|
108
107
|
|
109
|
-
this.
|
108
|
+
this.elem.appendChild(pathElem);
|
109
|
+
this.objectElems?.push(pathElem);
|
110
|
+
}
|
111
|
+
|
112
|
+
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
113
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
114
|
+
|
115
|
+
const textElem = document.createElementNS(svgNameSpace, 'text');
|
116
|
+
textElem.appendChild(document.createTextNode(text));
|
117
|
+
textElem.style.transform = `matrix(
|
118
|
+
${transform.a1}, ${transform.b1},
|
119
|
+
${transform.a2}, ${transform.b2},
|
120
|
+
${transform.a3}, ${transform.b3}
|
121
|
+
)`;
|
122
|
+
textElem.style.fontFamily = style.fontFamily;
|
123
|
+
textElem.style.fontVariant = style.fontVariant ?? '';
|
124
|
+
textElem.style.fontWeight = style.fontWeight ?? '';
|
125
|
+
textElem.style.fontSize = style.size + 'px';
|
126
|
+
textElem.style.fill = style.renderingStyle.fill.toHexString();
|
127
|
+
|
128
|
+
if (style.renderingStyle.stroke) {
|
129
|
+
const strokeStyle = style.renderingStyle.stroke;
|
130
|
+
textElem.style.stroke = strokeStyle.color.toHexString();
|
131
|
+
textElem.style.strokeWidth = strokeStyle.width + 'px';
|
132
|
+
}
|
133
|
+
|
134
|
+
this.elem.appendChild(textElem);
|
135
|
+
this.objectElems?.push(textElem);
|
110
136
|
}
|
111
137
|
|
112
138
|
public startObject(boundingBox: Rect2) {
|
@@ -116,13 +142,34 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
116
142
|
this.lastPath = null;
|
117
143
|
this.lastPathStart = null;
|
118
144
|
this.lastPathStyle = null;
|
145
|
+
this.objectElems = [];
|
119
146
|
}
|
120
147
|
|
121
|
-
public endObject() {
|
122
|
-
super.endObject();
|
148
|
+
public endObject(loaderData?: LoadSaveDataTable) {
|
149
|
+
super.endObject(loaderData);
|
123
150
|
|
124
151
|
// Don't extend paths across objects
|
125
152
|
this.addPathToSVG();
|
153
|
+
|
154
|
+
if (loaderData) {
|
155
|
+
// Restore any attributes unsupported by the app.
|
156
|
+
for (const elem of this.objectElems ?? []) {
|
157
|
+
const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
|
158
|
+
const styleAttrs = loaderData[svgStyleAttributesDataKey] as SVGLoaderUnknownStyleAttribute[]|undefined;
|
159
|
+
|
160
|
+
if (attrs) {
|
161
|
+
for (const [ attr, value ] of attrs) {
|
162
|
+
elem.setAttribute(attr, value);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
if (styleAttrs) {
|
167
|
+
for (const attr of styleAttrs) {
|
168
|
+
elem.style.setProperty(attr.key, attr.value, attr.priority);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
126
173
|
}
|
127
174
|
|
128
175
|
protected lineTo(point: Point2) {
|
@@ -175,7 +222,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
175
222
|
elem.setAttribute('cx', `${point.x}`);
|
176
223
|
elem.setAttribute('cy', `${point.y}`);
|
177
224
|
elem.setAttribute('r', '15');
|
178
|
-
this.
|
225
|
+
this.elem.appendChild(elem);
|
179
226
|
});
|
180
227
|
}
|
181
228
|
|
@@ -15,10 +15,7 @@ export const loadExpectExtensions = () => {
|
|
15
15
|
return {
|
16
16
|
pass,
|
17
17
|
message: () => {
|
18
|
-
|
19
|
-
return `Expected ${expected} not to .eq ${actual}. Options(${eqArgs})`;
|
20
|
-
}
|
21
|
-
return `Expected ${expected} to .eq ${actual}. Options(${eqArgs})`;
|
18
|
+
return `Expected ${pass ? '!' : ''}(${actual}).eq(${expected}). Options(${eqArgs})`;
|
22
19
|
},
|
23
20
|
};
|
24
21
|
},
|