@ankhorage/zora 25.11.1 → 26.0.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/CHANGELOG.md +12 -0
- package/README.md +1 -4
- package/dist/capabilities/index.js +65 -41
- package/dist/capabilities/index.js.map +3 -3
- package/dist/features/chess/adapters/inbound/ChessBoard.d.ts +1 -1
- package/dist/features/chess/adapters/inbound/ChessBoard.d.ts.map +1 -1
- package/dist/features/chess/adapters/inbound/ChessBoard.js +130 -83
- package/dist/features/chess/adapters/inbound/ChessBoard.js.map +1 -1
- package/dist/features/chess/meta.d.ts +97 -72
- package/dist/features/chess/meta.d.ts.map +1 -1
- package/dist/features/chess/meta.js +64 -40
- package/dist/features/chess/meta.js.map +1 -1
- package/dist/features/chess/public.d.ts +1 -7
- package/dist/features/chess/public.d.ts.map +1 -1
- package/dist/features/chess/public.js +0 -6
- package/dist/features/chess/public.js.map +1 -1
- package/dist/features/chess/utils/createChessBoardCellPressHandler.d.ts +6 -0
- package/dist/features/chess/utils/createChessBoardCellPressHandler.d.ts.map +1 -0
- package/dist/features/chess/utils/createChessBoardCellPressHandler.js +17 -0
- package/dist/features/chess/utils/createChessBoardCellPressHandler.js.map +1 -0
- package/dist/features/chess/utils/createChessBoardCells.d.ts +12 -0
- package/dist/features/chess/utils/createChessBoardCells.d.ts.map +1 -0
- package/dist/features/chess/utils/createChessBoardCells.js +28 -0
- package/dist/features/chess/utils/createChessBoardCells.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +2 -3
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +3 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.d.ts +5 -5
- package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.js +13 -7
- package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.js.map +1 -1
- package/dist/readerFree.d.ts +2 -2
- package/dist/readerFree.d.ts.map +1 -1
- package/dist/readerFree.js +1 -1
- package/dist/readerFree.js.map +1 -1
- package/dist/types/authoring.d.ts +2 -1
- package/dist/types/authoring.d.ts.map +1 -1
- package/dist/types/authoring.js.map +1 -1
- package/dist/types/chess.d.ts +27 -20
- package/dist/types/chess.d.ts.map +1 -1
- package/dist/types/chess.js.map +1 -1
- package/package.json +34 -94
- package/src/features/chess/adapters/inbound/ChessBoard.tsx +263 -112
- package/src/features/chess/meta.ts +64 -40
- package/src/features/chess/public.test.tsx +77 -228
- package/src/features/chess/public.ts +2 -9
- package/src/features/chess/utils/createChessBoardCellPressHandler.ts +30 -0
- package/src/features/chess/utils/createChessBoardCells.ts +54 -0
- package/src/features/chessTabletopOwnership.test.ts +3 -7
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.test.ts +1 -2
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +2 -3
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.test.ts +45 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +3 -1
- package/src/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.test.ts +65 -3
- package/src/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.ts +13 -7
- package/src/features/registry/componentMeta.test.ts +12 -1
- package/src/readerFree.ts +3 -15
- package/src/types/authoring.ts +2 -1
- package/src/types/chess.ts +28 -21
- package/web-dist/chunks/Accordion-91sjb3zh.js +453 -0
- package/web-dist/components/chess-board/ChessBoard.js +2 -1
- package/web-dist/components/graph-view/GraphView.js +878 -317
- package/web-dist/components/opening-book/OpeningBook.js +2 -1
- package/web-dist/manifest.json +8 -4
- package/dist/features/chess/utils/chessEngine.d.ts +0 -8
- package/dist/features/chess/utils/chessEngine.d.ts.map +0 -1
- package/dist/features/chess/utils/chessEngine.js +0 -71
- package/dist/features/chess/utils/chessEngine.js.map +0 -1
- package/dist/features/chess/utils/chessSquares.d.ts +0 -4
- package/dist/features/chess/utils/chessSquares.d.ts.map +0 -1
- package/dist/features/chess/utils/chessSquares.js +0 -3
- package/dist/features/chess/utils/chessSquares.js.map +0 -1
- package/dist/features/chess/utils/createBoardSquares.d.ts +0 -4
- package/dist/features/chess/utils/createBoardSquares.d.ts.map +0 -1
- package/dist/features/chess/utils/createBoardSquares.js +0 -9
- package/dist/features/chess/utils/createBoardSquares.js.map +0 -1
- package/dist/features/chess/utils/getSquareFile.d.ts +0 -4
- package/dist/features/chess/utils/getSquareFile.d.ts.map +0 -1
- package/dist/features/chess/utils/getSquareFile.js +0 -5
- package/dist/features/chess/utils/getSquareFile.js.map +0 -1
- package/dist/features/chess/utils/getSquareRank.d.ts +0 -4
- package/dist/features/chess/utils/getSquareRank.d.ts.map +0 -1
- package/dist/features/chess/utils/getSquareRank.js +0 -5
- package/dist/features/chess/utils/getSquareRank.js.map +0 -1
- package/dist/features/chess/utils/isLightSquare.d.ts +0 -4
- package/dist/features/chess/utils/isLightSquare.d.ts.map +0 -1
- package/dist/features/chess/utils/isLightSquare.js +0 -6
- package/dist/features/chess/utils/isLightSquare.js.map +0 -1
- package/src/features/chess/utils/chessEngine.test.ts +0 -35
- package/src/features/chess/utils/chessEngine.ts +0 -83
- package/src/features/chess/utils/chessSquares.ts +0 -6
- package/src/features/chess/utils/createBoardSquares.ts +0 -13
- package/src/features/chess/utils/getSquareFile.ts +0 -6
- package/src/features/chess/utils/getSquareRank.ts +0 -6
- package/src/features/chess/utils/isLightSquare.ts +0 -8
- package/web-dist/chunks/Accordion-xcm1qc39.js +0 -3813
|
@@ -9,42 +9,31 @@ export const chessBoardMeta = {
|
|
|
9
9
|
allowedChildren: [],
|
|
10
10
|
blueprint: {
|
|
11
11
|
label: 'Chess board',
|
|
12
|
-
defaultProps: {
|
|
12
|
+
defaultProps: {
|
|
13
|
+
columns: 8,
|
|
14
|
+
pieces: [],
|
|
15
|
+
orientation: 'white',
|
|
16
|
+
rows: 8,
|
|
17
|
+
showCoordinates: true,
|
|
18
|
+
},
|
|
13
19
|
},
|
|
14
20
|
events: {
|
|
15
|
-
|
|
16
|
-
label: '
|
|
17
|
-
eventType: 'chess.
|
|
18
|
-
payloadFields: [
|
|
21
|
+
cellPress: {
|
|
22
|
+
label: 'Cell press',
|
|
23
|
+
eventType: 'chess.cellPress',
|
|
24
|
+
payloadFields: [
|
|
25
|
+
{ path: 'column', type: 'number', label: 'Column' },
|
|
26
|
+
{ path: 'row', type: 'number', label: 'Row' },
|
|
27
|
+
],
|
|
19
28
|
},
|
|
20
29
|
moveAttempt: {
|
|
21
30
|
label: 'Move attempt',
|
|
22
31
|
eventType: 'chess.moveAttempt',
|
|
23
32
|
payloadFields: [
|
|
24
|
-
{ path: 'from', type: '
|
|
25
|
-
{ path: '
|
|
26
|
-
{ path: '
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
legalMove: {
|
|
30
|
-
label: 'Legal move',
|
|
31
|
-
eventType: 'chess.legalMove',
|
|
32
|
-
payloadFields: [
|
|
33
|
-
{ path: 'from', type: 'string', label: 'From' },
|
|
34
|
-
{ path: 'to', type: 'string', label: 'To' },
|
|
35
|
-
{ path: 'fen', type: 'string', label: 'Position' },
|
|
36
|
-
{ path: 'san', type: 'string', label: 'SAN' },
|
|
37
|
-
{ path: 'lan', type: 'string', label: 'LAN' },
|
|
38
|
-
{ path: 'promotion', type: 'string', label: 'Promotion' },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
invalidMove: {
|
|
42
|
-
label: 'Invalid move',
|
|
43
|
-
eventType: 'chess.invalidMove',
|
|
44
|
-
payloadFields: [
|
|
45
|
-
{ path: 'from', type: 'string', label: 'From' },
|
|
46
|
-
{ path: 'to', type: 'string', label: 'To' },
|
|
47
|
-
{ path: 'promotion', type: 'string', label: 'Promotion' },
|
|
33
|
+
{ path: 'from.column', type: 'number', label: 'From column' },
|
|
34
|
+
{ path: 'from.row', type: 'number', label: 'From row' },
|
|
35
|
+
{ path: 'to.column', type: 'number', label: 'To column' },
|
|
36
|
+
{ path: 'to.row', type: 'number', label: 'To row' },
|
|
48
37
|
],
|
|
49
38
|
},
|
|
50
39
|
},
|
|
@@ -56,19 +45,46 @@ export const chessBoardMeta = {
|
|
|
56
45
|
acceptsFallback: true,
|
|
57
46
|
acceptsTransforms: true,
|
|
58
47
|
},
|
|
48
|
+
selectedCell: {
|
|
49
|
+
label: 'Selected cell',
|
|
50
|
+
value: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
fields: [
|
|
53
|
+
{ path: 'column', type: 'number', label: 'Column' },
|
|
54
|
+
{ path: 'row', type: 'number', label: 'Row' },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
acceptsFallback: true,
|
|
58
|
+
acceptsTransforms: true,
|
|
59
|
+
},
|
|
59
60
|
legalTargets: {
|
|
60
61
|
label: 'Legal targets',
|
|
61
|
-
value: {
|
|
62
|
+
value: {
|
|
63
|
+
type: 'array',
|
|
64
|
+
itemType: 'object',
|
|
65
|
+
fields: [
|
|
66
|
+
{ path: '[].column', type: 'number', label: 'Column' },
|
|
67
|
+
{ path: '[].row', type: 'number', label: 'Row' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
62
70
|
acceptsFallback: true,
|
|
63
71
|
acceptsTransforms: true,
|
|
64
72
|
},
|
|
65
73
|
},
|
|
66
74
|
},
|
|
67
75
|
props: {
|
|
68
|
-
|
|
69
|
-
type: '
|
|
70
|
-
category: '
|
|
71
|
-
label: '
|
|
76
|
+
columns: {
|
|
77
|
+
type: 'number',
|
|
78
|
+
category: 'Board',
|
|
79
|
+
label: 'Columns',
|
|
80
|
+
default: 8,
|
|
81
|
+
authoring: { authority: 'instance' },
|
|
82
|
+
},
|
|
83
|
+
rows: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
category: 'Board',
|
|
86
|
+
label: 'Rows',
|
|
87
|
+
default: 8,
|
|
72
88
|
authoring: { authority: 'instance' },
|
|
73
89
|
},
|
|
74
90
|
pieces: {
|
|
@@ -86,10 +102,14 @@ export const chessBoardMeta = {
|
|
|
86
102
|
default: 'white',
|
|
87
103
|
authoring: { authority: 'instance' },
|
|
88
104
|
},
|
|
89
|
-
|
|
90
|
-
type: '
|
|
105
|
+
selectedCell: {
|
|
106
|
+
type: 'object',
|
|
91
107
|
category: 'Board',
|
|
92
|
-
label: 'Selected
|
|
108
|
+
label: 'Selected cell',
|
|
109
|
+
itemSchema: [
|
|
110
|
+
{ key: 'column', schema: { type: 'number', category: 'Cell' } },
|
|
111
|
+
{ key: 'row', schema: { type: 'number', category: 'Cell' } },
|
|
112
|
+
],
|
|
93
113
|
authoring: { authority: 'instance' },
|
|
94
114
|
},
|
|
95
115
|
legalTargets: {
|
|
@@ -97,6 +117,10 @@ export const chessBoardMeta = {
|
|
|
97
117
|
category: 'Board',
|
|
98
118
|
label: 'Legal targets',
|
|
99
119
|
default: [],
|
|
120
|
+
itemSchema: [
|
|
121
|
+
{ key: 'column', schema: { type: 'number', category: 'Cell' } },
|
|
122
|
+
{ key: 'row', schema: { type: 'number', category: 'Cell' } },
|
|
123
|
+
],
|
|
100
124
|
authoring: { authority: 'instance' },
|
|
101
125
|
},
|
|
102
126
|
showCoordinates: {
|
|
@@ -106,11 +130,11 @@ export const chessBoardMeta = {
|
|
|
106
130
|
default: false,
|
|
107
131
|
authoring: { authority: 'instance' },
|
|
108
132
|
},
|
|
109
|
-
|
|
133
|
+
resizable: {
|
|
110
134
|
type: 'boolean',
|
|
111
135
|
category: 'Behavior',
|
|
112
|
-
label: '
|
|
113
|
-
default:
|
|
136
|
+
label: 'Resizable',
|
|
137
|
+
default: false,
|
|
114
138
|
authoring: { authority: 'instance' },
|
|
115
139
|
},
|
|
116
140
|
disabled: {
|
|
@@ -1,248 +1,97 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
-
|
|
4
1
|
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
import { Window } from 'happy-dom';
|
|
6
|
-
import React, { act } from 'react';
|
|
7
|
-
import { createRoot } from 'react-dom/client';
|
|
8
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
9
2
|
|
|
10
3
|
import { chessBoardMeta } from './meta';
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
OpeningBook as OpeningBookComponent,
|
|
14
|
-
} from './public';
|
|
15
|
-
import { createBoardSquares } from './utils/createBoardSquares';
|
|
16
|
-
import { createChessBoardColorScheme } from './utils/createChessBoardColorScheme';
|
|
17
|
-
import { getSquareFile } from './utils/getSquareFile';
|
|
18
|
-
import { getSquareRank } from './utils/getSquareRank';
|
|
19
|
-
import { isLightSquare } from './utils/isLightSquare';
|
|
20
|
-
|
|
21
|
-
const webDistRoot = join(import.meta.dir, '../../../web-dist');
|
|
22
|
-
const load = (path: string) => import(pathToFileURL(join(webDistRoot, path)).href);
|
|
4
|
+
import { createChessBoardCellPressHandler } from './utils/createChessBoardCellPressHandler';
|
|
5
|
+
import { createChessBoardCells } from './utils/createChessBoardCells';
|
|
23
6
|
|
|
24
7
|
describe('Chess presentation', () => {
|
|
25
|
-
test('
|
|
26
|
-
expect(
|
|
27
|
-
expect(
|
|
28
|
-
expect(
|
|
29
|
-
expect(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('preserves FEN and validated-move authoring metadata', () => {
|
|
35
|
-
expect(chessBoardMeta.props.fen.type).toBe('string');
|
|
36
|
-
expect(chessBoardMeta.props.validateMoves.default).toBe(true);
|
|
37
|
-
expect(chessBoardMeta.events.legalMove.payloadFields.map((field) => field.path)).toEqual([
|
|
38
|
-
'from',
|
|
39
|
-
'to',
|
|
40
|
-
'fen',
|
|
41
|
-
'san',
|
|
42
|
-
'lan',
|
|
43
|
-
'promotion',
|
|
44
|
-
]);
|
|
45
|
-
expect(chessBoardMeta.events.invalidMove.payloadFields.map((field) => field.path)).toEqual([
|
|
46
|
-
'from',
|
|
47
|
-
'to',
|
|
48
|
-
'promotion',
|
|
8
|
+
test('declares caller-owned dimensions, cells, and move intents without rule-engine events', () => {
|
|
9
|
+
expect(chessBoardMeta.blueprint.defaultProps).toMatchObject({ columns: 8, rows: 8 });
|
|
10
|
+
expect(chessBoardMeta.props.columns.default).toBe(8);
|
|
11
|
+
expect(chessBoardMeta.props.rows.default).toBe(8);
|
|
12
|
+
expect(chessBoardMeta.events.cellPress.payloadFields).toEqual([
|
|
13
|
+
{ path: 'column', type: 'number', label: 'Column' },
|
|
14
|
+
{ path: 'row', type: 'number', label: 'Row' },
|
|
49
15
|
]);
|
|
16
|
+
expect(chessBoardMeta.events).not.toHaveProperty('legalMove');
|
|
17
|
+
expect(chessBoardMeta.events).not.toHaveProperty('invalidMove');
|
|
50
18
|
});
|
|
51
19
|
|
|
52
|
-
test('
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
59
|
-
const markup = renderToStaticMarkup(
|
|
60
|
-
<>
|
|
61
|
-
<ChessBoard
|
|
62
|
-
colorScheme={{ selectedSquare: '#123456', legalTarget: '#234567' }}
|
|
63
|
-
legalTargets={['e4']}
|
|
64
|
-
pieces={[{ color: 'white', piece: 'p', square: 'e2' }]}
|
|
65
|
-
renderPiece={() => <div data-testid="custom-piece" />}
|
|
66
|
-
selectedSquare="e2"
|
|
67
|
-
showCoordinates
|
|
68
|
-
testID="board"
|
|
69
|
-
/>
|
|
70
|
-
<OpeningBook
|
|
71
|
-
moves={[
|
|
72
|
-
{
|
|
73
|
-
san: 'Nf3',
|
|
74
|
-
eco: 'A04',
|
|
75
|
-
name: 'Reti Opening',
|
|
76
|
-
games: 42,
|
|
77
|
-
whiteWinRate: 0.52,
|
|
78
|
-
drawRate: 0.2,
|
|
79
|
-
blackWinRate: 0.28,
|
|
80
|
-
},
|
|
81
|
-
]}
|
|
82
|
-
testID="book"
|
|
83
|
-
/>
|
|
84
|
-
</>,
|
|
85
|
-
);
|
|
86
|
-
const browser = new Window();
|
|
87
|
-
browser.document.body.innerHTML = markup;
|
|
88
|
-
expect(browser.document.querySelectorAll('[data-testid^="board-square-"]')).toHaveLength(64);
|
|
89
|
-
expect(
|
|
90
|
-
browser.document.querySelector('[data-testid="board-square-e2"]')?.textContent,
|
|
91
|
-
).toContain('e2');
|
|
92
|
-
expect(
|
|
93
|
-
browser.document.querySelector('[data-testid="custom-piece"]')?.parentElement?.className,
|
|
94
|
-
).toContain('css-view');
|
|
95
|
-
expect(
|
|
96
|
-
browser.document.querySelector('[data-testid="board-square-e2"]')?.getAttribute('style'),
|
|
97
|
-
).toContain('background-color:rgba(18,52,86,1.00)');
|
|
98
|
-
expect(
|
|
99
|
-
browser.document.querySelector('[data-testid="board-square-e4"]')?.getAttribute('style'),
|
|
100
|
-
).toContain('background-color:rgba(35,69,103,1.00)');
|
|
101
|
-
expect(browser.document.querySelector('[data-testid="book-move-Nf3"]')?.textContent).toContain(
|
|
102
|
-
'A04 · Reti Opening · 42 games',
|
|
103
|
-
);
|
|
104
|
-
expect(browser.document.querySelector('[data-testid="book-move-Nf3"]')?.textContent).toContain(
|
|
105
|
-
'W 52% D 20% B 28%',
|
|
106
|
-
);
|
|
107
|
-
browser.close();
|
|
20
|
+
test('uses the shared GridView presentation boundary rather than a chess rules engine', async () => {
|
|
21
|
+
const source = await Bun.file('src/features/chess/adapters/inbound/ChessBoard.tsx').text();
|
|
22
|
+
|
|
23
|
+
expect(source).toContain("import { GridView } from '../../../grid-view/public';");
|
|
24
|
+
expect(source).not.toContain('chess.js');
|
|
25
|
+
expect(source).not.toContain('fen');
|
|
108
26
|
});
|
|
109
27
|
|
|
110
|
-
test('
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
document: browser.document,
|
|
122
|
-
Node: browser.Node,
|
|
123
|
-
navigator: browser.navigator,
|
|
124
|
-
IS_REACT_ACT_ENVIRONMENT: true,
|
|
28
|
+
test('maps non-square boards into opposite GridView coordinates for white and black orientation', () => {
|
|
29
|
+
const white = createChessBoardCells(3, 2, 'white');
|
|
30
|
+
const black = createChessBoardCells(3, 2, 'black');
|
|
31
|
+
|
|
32
|
+
expect(white.find((cell) => cell.column === 1 && cell.row === 2)).toMatchObject({
|
|
33
|
+
x: 0,
|
|
34
|
+
y: 0,
|
|
35
|
+
});
|
|
36
|
+
expect(white.find((cell) => cell.column === 3 && cell.row === 1)).toMatchObject({
|
|
37
|
+
x: 2,
|
|
38
|
+
y: 1,
|
|
125
39
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
40
|
+
expect(black.find((cell) => cell.column === 3 && cell.row === 1)).toMatchObject({
|
|
41
|
+
x: 0,
|
|
42
|
+
y: 0,
|
|
43
|
+
});
|
|
44
|
+
expect(black.find((cell) => cell.column === 1 && cell.row === 2)).toMatchObject({
|
|
45
|
+
x: 2,
|
|
46
|
+
y: 1,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
131
49
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
onLegalMove={(move) => legal.push(`${move.lan}:${move.san}`)}
|
|
139
|
-
selectedSquare="e2"
|
|
140
|
-
showCoordinates
|
|
141
|
-
testID="fen-board"
|
|
142
|
-
/>,
|
|
143
|
-
),
|
|
144
|
-
);
|
|
50
|
+
test('accepts only the documented 2 through 24 presentation dimensions', () => {
|
|
51
|
+
expect(createChessBoardCells(2, 24, 'white')).toHaveLength(48);
|
|
52
|
+
expect(() => createChessBoardCells(1, 8, 'white')).toThrow(RangeError);
|
|
53
|
+
expect(() => createChessBoardCells(8, 25, 'black')).toThrow(RangeError);
|
|
54
|
+
expect(() => createChessBoardCells(2.5, 8, 'white')).toThrow(RangeError);
|
|
55
|
+
});
|
|
145
56
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
57
|
+
test('emits caller-owned press and move intents without acting in passive mode', () => {
|
|
58
|
+
const presses: unknown[] = [];
|
|
59
|
+
const attempts: unknown[] = [];
|
|
60
|
+
const active = createChessBoardCellPressHandler({
|
|
61
|
+
onCellPress: (cell) => presses.push(cell),
|
|
62
|
+
onMoveAttempt: (attempt) => attempts.push(attempt),
|
|
63
|
+
selectedCell: { column: 2, row: 2 },
|
|
64
|
+
});
|
|
150
65
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
66
|
+
active({ column: 3, row: 4 });
|
|
67
|
+
createChessBoardCellPressHandler({
|
|
68
|
+
interactionPolicy: 'passive',
|
|
69
|
+
onCellPress: (cell) => presses.push(cell),
|
|
70
|
+
onMoveAttempt: (attempt) => attempts.push(attempt),
|
|
71
|
+
selectedCell: { column: 2, row: 2 },
|
|
72
|
+
})({ column: 4, row: 4 });
|
|
157
73
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.querySelector('[data-testid="fen-board-square-e5"]')
|
|
161
|
-
?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
|
|
162
|
-
});
|
|
163
|
-
expect(invalid).toEqual(['e2-e5']);
|
|
164
|
-
} finally {
|
|
165
|
-
act(() => root.unmount());
|
|
166
|
-
browser.close();
|
|
167
|
-
for (const [key, descriptor] of previous) {
|
|
168
|
-
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
169
|
-
else Reflect.deleteProperty(globalThis, key);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
74
|
+
expect(presses).toEqual([{ column: 3, row: 4 }]);
|
|
75
|
+
expect(attempts).toEqual([{ from: { column: 2, row: 2 }, to: { column: 3, row: 4 } }]);
|
|
172
76
|
});
|
|
173
77
|
|
|
174
|
-
test('
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
IS_REACT_ACT_ENVIRONMENT: true,
|
|
78
|
+
test('describes selected cells and legal targets as cell objects in manifest metadata', () => {
|
|
79
|
+
expect(chessBoardMeta.props.selectedCell).toMatchObject({
|
|
80
|
+
type: 'object',
|
|
81
|
+
itemSchema: [{ key: 'column' }, { key: 'row' }],
|
|
82
|
+
});
|
|
83
|
+
expect(chessBoardMeta.props.legalTargets).toMatchObject({
|
|
84
|
+
type: 'array',
|
|
85
|
+
itemSchema: [{ key: 'column' }, { key: 'row' }],
|
|
86
|
+
});
|
|
87
|
+
expect(chessBoardMeta.bindings.props.selectedCell.value).toMatchObject({
|
|
88
|
+
type: 'object',
|
|
89
|
+
fields: [{ path: 'column' }, { path: 'row' }],
|
|
90
|
+
});
|
|
91
|
+
expect(chessBoardMeta.bindings.props.legalTargets.value).toMatchObject({
|
|
92
|
+
type: 'array',
|
|
93
|
+
itemType: 'object',
|
|
94
|
+
fields: [{ path: '[].column' }, { path: '[].row' }],
|
|
192
95
|
});
|
|
193
|
-
const host = document.createElement('div');
|
|
194
|
-
document.body.appendChild(host);
|
|
195
|
-
const squares: string[] = [];
|
|
196
|
-
const moves: string[] = [];
|
|
197
|
-
const book: string[] = [];
|
|
198
|
-
const root = createRoot(host);
|
|
199
|
-
const render = (interactionPolicy: 'active' | 'passive') => (
|
|
200
|
-
<>
|
|
201
|
-
<ChessBoard
|
|
202
|
-
interactionPolicy={interactionPolicy}
|
|
203
|
-
selectedSquare="e2"
|
|
204
|
-
testID="board"
|
|
205
|
-
onSquarePress={(square) => squares.push(square)}
|
|
206
|
-
onMoveAttempt={(move) => moves.push(`${move.from}-${move.to}`)}
|
|
207
|
-
/>
|
|
208
|
-
<OpeningBook
|
|
209
|
-
interactionPolicy={interactionPolicy}
|
|
210
|
-
moves={[{ san: 'e4' }]}
|
|
211
|
-
onMovePress={(move) => book.push(move.san)}
|
|
212
|
-
testID="book"
|
|
213
|
-
/>
|
|
214
|
-
</>
|
|
215
|
-
);
|
|
216
|
-
try {
|
|
217
|
-
act(() => root.render(render('passive')));
|
|
218
|
-
act(() => {
|
|
219
|
-
host
|
|
220
|
-
.querySelector('[data-testid="board-square-e4"]')
|
|
221
|
-
?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
|
|
222
|
-
host
|
|
223
|
-
.querySelector('[data-testid="book-move-e4"]')
|
|
224
|
-
?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
|
|
225
|
-
});
|
|
226
|
-
expect([squares, moves, book]).toEqual([[], [], []]);
|
|
227
|
-
act(() => root.render(render('active')));
|
|
228
|
-
act(() => {
|
|
229
|
-
host
|
|
230
|
-
.querySelector('[data-testid="board-square-e4"]')
|
|
231
|
-
?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
|
|
232
|
-
host
|
|
233
|
-
.querySelector('[data-testid="book-move-e4"]')
|
|
234
|
-
?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
|
|
235
|
-
});
|
|
236
|
-
expect(squares).toEqual(['e4']);
|
|
237
|
-
expect(moves).toEqual(['e2-e4']);
|
|
238
|
-
expect(book).toEqual(['e4']);
|
|
239
|
-
} finally {
|
|
240
|
-
act(() => root.unmount());
|
|
241
|
-
browser.close();
|
|
242
|
-
for (const [key, descriptor] of previous) {
|
|
243
|
-
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
244
|
-
else Reflect.deleteProperty(globalThis, key);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
96
|
});
|
|
248
97
|
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
export type {
|
|
2
|
+
ChessBoardCell,
|
|
2
3
|
ChessBoardColorOverrides,
|
|
3
4
|
ChessBoardColorScheme,
|
|
5
|
+
ChessBoardDimensions,
|
|
4
6
|
ChessBoardOrientation,
|
|
5
7
|
ChessBoardProps,
|
|
6
8
|
ChessMoveAttempt,
|
|
7
|
-
ChessMoveResult,
|
|
8
9
|
ChessPieceCode,
|
|
9
10
|
ChessPieceRenderContext,
|
|
10
11
|
ChessPieceRenderer,
|
|
11
12
|
ChessPieceState,
|
|
12
|
-
ChessPromotionPiece,
|
|
13
|
-
ChessSquareId,
|
|
14
13
|
OpeningBookColorOverrides,
|
|
15
14
|
OpeningBookColorScheme,
|
|
16
15
|
OpeningBookMove,
|
|
@@ -19,12 +18,6 @@ export type {
|
|
|
19
18
|
export { ChessBoard } from './adapters/inbound/ChessBoard';
|
|
20
19
|
export { OpeningBook } from './adapters/inbound/OpeningBook';
|
|
21
20
|
export { chessBoardMeta, openingBookMeta } from './meta';
|
|
22
|
-
export { getLegalTargets, readChessPieces, tryMove } from './utils/chessEngine';
|
|
23
|
-
export { chessSquares } from './utils/chessSquares';
|
|
24
|
-
export { createBoardSquares } from './utils/createBoardSquares';
|
|
25
21
|
export type { ChessColorThemeShape } from './utils/createChessBoardColorScheme';
|
|
26
22
|
export { createChessBoardColorScheme } from './utils/createChessBoardColorScheme';
|
|
27
23
|
export { createOpeningBookColorScheme } from './utils/createOpeningBookColorScheme';
|
|
28
|
-
export { getSquareFile } from './utils/getSquareFile';
|
|
29
|
-
export { getSquareRank } from './utils/getSquareRank';
|
|
30
|
-
export { isLightSquare } from './utils/isLightSquare';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChessBoardCell, ChessBoardProps } from '../../../types/chess';
|
|
2
|
+
|
|
3
|
+
type ChessBoardCellPressHandlerOptions = Pick<
|
|
4
|
+
ChessBoardProps,
|
|
5
|
+
'disabled' | 'interactionPolicy' | 'onCellPress' | 'onMoveAttempt' | 'selectedCell'
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
/*** Creates the caller-owned cell and move intent boundary for an interactive chess presentation. */
|
|
9
|
+
export function createChessBoardCellPressHandler({
|
|
10
|
+
disabled,
|
|
11
|
+
interactionPolicy,
|
|
12
|
+
onCellPress,
|
|
13
|
+
onMoveAttempt,
|
|
14
|
+
selectedCell,
|
|
15
|
+
}: ChessBoardCellPressHandlerOptions): (cell: ChessBoardCell) => void {
|
|
16
|
+
return (cell) => {
|
|
17
|
+
if (disabled || interactionPolicy === 'passive') return;
|
|
18
|
+
|
|
19
|
+
onCellPress?.(cell);
|
|
20
|
+
if (selectedCell === null || selectedCell === undefined || cellsEqual(selectedCell, cell)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
onMoveAttempt?.({ from: selectedCell, to: cell });
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*** Compares caller-owned coordinates without adding chess-rule semantics. */
|
|
28
|
+
function cellsEqual(left: ChessBoardCell, right: ChessBoardCell): boolean {
|
|
29
|
+
return left.column === right.column && left.row === right.row;
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ChessBoardCell, ChessBoardOrientation } from '../../../types/chess';
|
|
2
|
+
|
|
3
|
+
const minimumDimension = 2;
|
|
4
|
+
const maximumDimension = 24;
|
|
5
|
+
|
|
6
|
+
type ChessBoardGridCell = ChessBoardCell & {
|
|
7
|
+
readonly height: 1;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly width: 1;
|
|
10
|
+
readonly x: number;
|
|
11
|
+
readonly y: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/*** Creates bounded GridView cells whose coordinates represent the requested chess orientation. */
|
|
15
|
+
export function createChessBoardCells(
|
|
16
|
+
columns: number,
|
|
17
|
+
rows: number,
|
|
18
|
+
orientation: ChessBoardOrientation,
|
|
19
|
+
): readonly ChessBoardGridCell[] {
|
|
20
|
+
validateChessBoardDimensions(columns, rows);
|
|
21
|
+
|
|
22
|
+
return Array.from({ length: rows }, (_, rowIndex) =>
|
|
23
|
+
Array.from({ length: columns }, (_, columnIndex) => {
|
|
24
|
+
const column = columnIndex + 1;
|
|
25
|
+
const row = rowIndex + 1;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
column,
|
|
29
|
+
height: 1 as const,
|
|
30
|
+
id: `${column}:${row}`,
|
|
31
|
+
row,
|
|
32
|
+
width: 1 as const,
|
|
33
|
+
x: orientation === 'white' ? columnIndex : columns - column,
|
|
34
|
+
y: orientation === 'white' ? rows - row : rowIndex,
|
|
35
|
+
};
|
|
36
|
+
}),
|
|
37
|
+
).flat();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*** Rejects dimensions outside the presentation contract before GridView receives them. */
|
|
41
|
+
function validateChessBoardDimensions(columns: number, rows: number): void {
|
|
42
|
+
if (
|
|
43
|
+
![columns, rows].every(
|
|
44
|
+
(dimension) =>
|
|
45
|
+
Number.isInteger(dimension) &&
|
|
46
|
+
dimension >= minimumDimension &&
|
|
47
|
+
dimension <= maximumDimension,
|
|
48
|
+
)
|
|
49
|
+
) {
|
|
50
|
+
throw new RangeError(
|
|
51
|
+
`ChessBoard dimensions must be whole numbers from ${minimumDimension} through ${maximumDimension}.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -32,17 +32,13 @@ test('Chess and Tabletop implementation modules own one matching runtime export
|
|
|
32
32
|
);
|
|
33
33
|
return [];
|
|
34
34
|
});
|
|
35
|
-
|
|
36
|
-
name === 'chessEngine.ts'
|
|
37
|
-
? ['getLegalTargets', 'readChessPieces', 'tryMove']
|
|
38
|
-
: [parse(name).name];
|
|
39
|
-
expect(runtimeExports.sort(), join(directory, name)).toEqual(expectedExports.sort());
|
|
35
|
+
expect(runtimeExports.sort(), join(directory, name)).toEqual([parse(name).name]);
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
38
|
}
|
|
43
39
|
});
|
|
44
40
|
|
|
45
|
-
test('Chess and Tabletop source graphs depend only on their intentional
|
|
41
|
+
test('Chess and Tabletop source graphs depend only on their intentional presentation owners', () => {
|
|
46
42
|
const sourcePaths = featureRoots.flatMap((featureRoot) => {
|
|
47
43
|
const visit = (directory: string): string[] =>
|
|
48
44
|
readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
@@ -67,7 +63,7 @@ test('Chess and Tabletop source graphs depend only on their intentional runtime
|
|
|
67
63
|
for (const specifier of imports) {
|
|
68
64
|
if (specifier.startsWith('.')) continue;
|
|
69
65
|
expect(
|
|
70
|
-
['react', 'react-native', '
|
|
66
|
+
['react', 'react-native', '@ankhorage/color-theory'],
|
|
71
67
|
`${path}: ${specifier}`,
|
|
72
68
|
).toContain(specifier);
|
|
73
69
|
}
|
|
@@ -243,7 +243,6 @@ test('measures requested rendered nodes without exposing Cytoscape', () => {
|
|
|
243
243
|
try {
|
|
244
244
|
const measurement = controller.measureNodes?.(['a', 'missing']);
|
|
245
245
|
const node = cy.getElementById('a');
|
|
246
|
-
const bounds = node.renderedBoundingBox();
|
|
247
246
|
|
|
248
247
|
expect(measurement).toEqual({
|
|
249
248
|
viewport: { width: 800, height: 600 },
|
|
@@ -251,7 +250,7 @@ test('measures requested rendered nodes without exposing Cytoscape', () => {
|
|
|
251
250
|
{
|
|
252
251
|
id: 'a',
|
|
253
252
|
position: node.renderedPosition(),
|
|
254
|
-
size: { width:
|
|
253
|
+
size: { width: node.renderedOuterWidth(), height: node.renderedOuterHeight() },
|
|
255
254
|
},
|
|
256
255
|
],
|
|
257
256
|
});
|