scrabble-solver 2.18.10-alpha.1 → 2.19.0-alpha.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 (191) hide show
  1. package/.env +1 -0
  2. package/.next/BUILD_ID +1 -0
  3. package/.next/build-manifest.json +42 -0
  4. package/.next/dynamic-css-manifest.json +8 -0
  5. package/.next/export-marker.json +6 -0
  6. package/.next/images-manifest.json +68 -0
  7. package/.next/package.json +1 -0
  8. package/.next/prerender-manifest.json +28 -0
  9. package/.next/react-loadable-manifest.json +182 -0
  10. package/.next/required-server-files.js +371 -0
  11. package/.next/required-server-files.json +371 -0
  12. package/.next/routes-manifest.json +119 -0
  13. package/.next/server/chunks/30.js +6 -0
  14. package/.next/server/chunks/309.js +1 -0
  15. package/.next/server/chunks/344.js +1 -0
  16. package/.next/server/chunks/371.js +1 -0
  17. package/.next/server/chunks/465.js +1 -0
  18. package/.next/server/chunks/546.js +1 -0
  19. package/.next/server/chunks/612.js +5 -0
  20. package/.next/server/chunks/623.js +1 -0
  21. package/.next/server/chunks/641.js +1 -0
  22. package/.next/server/chunks/67.js +1 -0
  23. package/.next/server/chunks/671.js +1 -0
  24. package/.next/server/chunks/692.js +1 -0
  25. package/.next/server/chunks/705.js +1 -0
  26. package/.next/server/chunks/712.js +1 -0
  27. package/.next/server/chunks/715.js +1 -0
  28. package/.next/server/chunks/812.js +1 -0
  29. package/.next/server/chunks/846.js +1 -0
  30. package/.next/server/chunks/853.js +15 -0
  31. package/.next/server/chunks/886.js +1 -0
  32. package/.next/server/chunks/923.js +1 -0
  33. package/.next/server/chunks/942.js +1 -0
  34. package/.next/server/chunks/953.js +1 -0
  35. package/.next/server/chunks/961.js +1 -0
  36. package/.next/server/chunks/968.js +1 -0
  37. package/.next/server/chunks/97.js +1 -0
  38. package/.next/server/dynamic-css-manifest.js +1 -0
  39. package/.next/server/functions-config-manifest.json +4 -0
  40. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  41. package/.next/server/middleware-build-manifest.js +1 -0
  42. package/.next/server/middleware-manifest.json +6 -0
  43. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  44. package/.next/server/next-font-manifest.js +1 -0
  45. package/.next/server/next-font-manifest.json +1 -0
  46. package/.next/server/pages/404.html +29 -0
  47. package/.next/server/pages/404.js +28 -0
  48. package/.next/server/pages/500.html +29 -0
  49. package/.next/server/pages/_app.js +1 -0
  50. package/.next/server/pages/_document.js +28 -0
  51. package/.next/server/pages/_error.js +28 -0
  52. package/.next/server/pages/api/dictionary/[locale]/[word].js +2 -0
  53. package/.next/server/pages/api/dictionary/[locale].js +1 -0
  54. package/.next/server/pages/api/solve.js +1 -0
  55. package/.next/server/pages/api/verify.js +1 -0
  56. package/.next/server/pages/api/visit.js +1 -0
  57. package/.next/server/pages/index.html +29 -0
  58. package/.next/server/pages/index.js +30 -0
  59. package/.next/server/pages/index.json +1 -0
  60. package/.next/server/pages/index.meta +1 -0
  61. package/.next/server/pages-manifest.json +12 -0
  62. package/.next/server/prefetch-hints.json +1 -0
  63. package/.next/server/preview-props.json +5 -0
  64. package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.html +29 -0
  65. package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.json +1 -0
  66. package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.meta +1 -0
  67. package/.next/server/webpack-api-runtime.js +1 -0
  68. package/.next/server/webpack-runtime.js +1 -0
  69. package/.next/static/Ng_NN9gQywZowmg2O0pgm/_buildManifest.js +1 -0
  70. package/.next/static/Ng_NN9gQywZowmg2O0pgm/_ssgManifest.js +1 -0
  71. package/.next/static/chunks/100.52dda92e5e8a952a.js +1 -0
  72. package/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  73. package/.next/static/chunks/307.8fdeb1732d749981.js +1 -0
  74. package/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  75. package/.next/static/chunks/363.25b64181ebca205d.js +1 -0
  76. package/.next/static/chunks/422.8327da4bbddd0842.js +1 -0
  77. package/.next/static/chunks/519.9f9c2d29933bf2ef.js +1 -0
  78. package/.next/static/chunks/541.c69629e483176ad7.js +1 -0
  79. package/.next/static/chunks/628.3175e816bc17d5c7.js +1 -0
  80. package/.next/static/chunks/631.3623a2830ab30d1e.js +1 -0
  81. package/.next/static/chunks/663.cfbde775630d10fd.js +1 -0
  82. package/.next/static/chunks/693.55a42abad20cbdaa.js +1 -0
  83. package/.next/static/chunks/73.73bfdde6a7a9b42b.js +1 -0
  84. package/.next/static/chunks/744-5baf05ff91b58ebe.js +3 -0
  85. package/.next/static/chunks/820.e8f3e1c85e1657ec.js +1 -0
  86. package/.next/static/chunks/827.d01b6d69af340ba4.js +1 -0
  87. package/.next/static/chunks/865.5265cef552d53595.js +1 -0
  88. package/.next/static/chunks/912.b802c1066a130cf6.js +1 -0
  89. package/.next/static/chunks/939.63d71e74e359a311.js +1 -0
  90. package/.next/static/chunks/980.dd35e807b18063c1.js +1 -0
  91. package/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  92. package/.next/static/chunks/framework-86d3b250d278cea9.js +1 -0
  93. package/.next/static/chunks/main-738e097cd313908f.js +5 -0
  94. package/.next/static/chunks/pages/404-8013c66eb40999e8.js +1 -0
  95. package/.next/static/chunks/pages/_app-aee589671f9cc846.js +1 -0
  96. package/.next/static/chunks/pages/_error-f0290a98eb3ed0ec.js +1 -0
  97. package/.next/static/chunks/pages/index-a96995844e778716.js +1 -0
  98. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  99. package/.next/static/chunks/webpack-4bca6879a7d98e24.js +1 -0
  100. package/.next/static/css/2d03f0da2c678dfc.css +1 -0
  101. package/.next/static/css/4dbcacc1122ac6b9.css +2 -0
  102. package/.next/static/css/7b6724d2df70f48f.css +1 -0
  103. package/.next/static/css/90e8d51d917ed7e3.css +1 -0
  104. package/.next/static/css/98051ed25076b896.css +1 -0
  105. package/.next/static/css/b736be3bd02b5d99.css +1 -0
  106. package/.next/static/css/ca311e6c030663d4.css +1 -0
  107. package/.next/static/css/cd4d7f2458487399.css +1 -0
  108. package/.next/static/css/dee878eb64099d92.css +1 -0
  109. package/README.md +5 -217
  110. package/bin/scrabble-solver.js +35 -4
  111. package/next.config.js +60 -0
  112. package/package.json +92 -52
  113. package/public/favicon.ico +0 -0
  114. package/public/icons/apple-touch-icon-114x114.png +0 -0
  115. package/public/icons/apple-touch-icon-120x120.png +0 -0
  116. package/public/icons/apple-touch-icon-144x144.png +0 -0
  117. package/public/icons/apple-touch-icon-152x152.png +0 -0
  118. package/public/icons/apple-touch-icon-57x57.png +0 -0
  119. package/public/icons/apple-touch-icon-60x60.png +0 -0
  120. package/public/icons/apple-touch-icon-72x72.png +0 -0
  121. package/public/icons/apple-touch-icon-76x76.png +0 -0
  122. package/public/icons/favicon-128.png +0 -0
  123. package/public/icons/favicon-16x16.png +0 -0
  124. package/public/icons/favicon-196x196.png +0 -0
  125. package/public/icons/favicon-32x32.png +0 -0
  126. package/public/icons/favicon-96x96.png +0 -0
  127. package/public/icons/favicon.svg +6 -0
  128. package/public/icons/mstile-144x144.png +0 -0
  129. package/public/icons/mstile-150x150.png +0 -0
  130. package/public/icons/mstile-310x150.png +0 -0
  131. package/public/icons/mstile-310x310.png +0 -0
  132. package/public/icons/mstile-70x70.png +0 -0
  133. package/public/llms.txt +11 -0
  134. package/public/logo.svg +62 -0
  135. package/public/og.png +0 -0
  136. package/public/robots.txt +5 -0
  137. package/public/service-worker.js +2 -0
  138. package/public/site.webmanifest +14 -0
  139. package/public/sitemap.xml +1 -0
  140. package/.gitattributes +0 -2
  141. package/.next/trace +0 -1
  142. package/.next/trace-build +0 -1
  143. package/.oxfmtrc.json +0 -17
  144. package/.oxlintrc.json +0 -314
  145. package/CLAUDE.md +0 -134
  146. package/HISTORY.md +0 -13
  147. package/bun-test-globals.d.ts +0 -1
  148. package/bun.lock +0 -2150
  149. package/bun.test.preload.ts +0 -21
  150. package/bunfig.toml +0 -9
  151. package/e2e/app.spec.ts +0 -124
  152. package/e2e/bugs/112-character-bonus.spec.ts +0 -25
  153. package/e2e/bugs/129-esc-sidebar.spec.ts +0 -17
  154. package/e2e/bugs/2-q-tile-in-english.spec.ts +0 -19
  155. package/e2e/bugs/3-x-tile-in-polish.spec.ts +0 -19
  156. package/e2e/bugs/30-click-inside-radio-icon.spec.ts +0 -25
  157. package/e2e/bugs/333-disabled-radio-cursor.spec.ts +0 -18
  158. package/e2e/bugs/342-results-sorting.spec.ts +0 -30
  159. package/e2e/bugs/352-dictionary-race-condition.spec.ts +0 -27
  160. package/e2e/bugs/363-dictionary-input-unique-words.spec.ts +0 -27
  161. package/e2e/bugs/380-transliteration-when-pasting.spec.ts +0 -55
  162. package/e2e/bugs/398-digraphs-in-created-words.spec.ts +0 -23
  163. package/e2e/bugs/401-board-state-on-game-change.spec.ts +0 -24
  164. package/e2e/bugs/445-tile-text-selection.spec.ts +0 -38
  165. package/e2e/bugs/451-results-list-gap-hover.spec.ts +0 -35
  166. package/e2e/bugs/452-stale-translations-cache.spec.ts +0 -32
  167. package/e2e/bugs/461-regexp-highlight.spec.ts +0 -33
  168. package/e2e/features/198-highlight-unreachable-cells.spec.ts +0 -84
  169. package/e2e/features/427-highlight-created-words.spec.ts +0 -86
  170. package/e2e/features/430-highlight-unreachable-cells-results.spec.ts +0 -74
  171. package/e2e/features/91-hover-remaining-tile-highlights.spec.ts +0 -97
  172. package/e2e/features/hidden-coordinates-migration.spec.ts +0 -27
  173. package/e2e/features/result-candidate-picker.spec.ts +0 -21
  174. package/e2e/features/results-interactions.spec.ts +0 -170
  175. package/e2e/features/solver-paths.spec.ts +0 -45
  176. package/e2e/features/words-dictionary-search.spec.ts +0 -36
  177. package/e2e/features/words-empty-state.spec.ts +0 -42
  178. package/e2e/features/words-interactions.spec.ts +0 -179
  179. package/e2e/features/words-preview.spec.ts +0 -140
  180. package/e2e/features/words-table-filtering.spec.ts +0 -22
  181. package/e2e/features/words-table-sorting.spec.ts +0 -33
  182. package/e2e/lib/actions.ts +0 -202
  183. package/e2e/lib/index.ts +0 -2
  184. package/e2e/lib/selectors.ts +0 -108
  185. package/e2e/tsconfig.json +0 -10
  186. package/nx.json +0 -39
  187. package/playwright.config.ts +0 -30
  188. package/project.json +0 -3
  189. package/screencast.gif +0 -0
  190. package/scripts/record-screencast.ts +0 -347
  191. package/tsconfig.json +0 -19
@@ -1,21 +0,0 @@
1
- import { plugin } from 'bun';
2
-
3
- plugin({
4
- name: 'scss-mock',
5
- setup(build) {
6
- build.onLoad({ filter: /\.scss$/ }, () => ({
7
- contents: `
8
- const proxy = new Proxy({}, {
9
- get(_, key) {
10
- if (typeof key === 'symbol') return undefined;
11
- if (key === 'default') return proxy;
12
- if (key === '__esModule') return true;
13
- return key;
14
- },
15
- });
16
- export default proxy;
17
- `,
18
- loader: 'js',
19
- }));
20
- },
21
- });
package/bunfig.toml DELETED
@@ -1,9 +0,0 @@
1
- # Keep the classic flat node_modules layout. With Bun's newer "isolated" layout,
2
- # `next build` fails: webpack starts bundling unzipper (used by word-lists) and
3
- # errors on @aws-sdk/client-s3 - an optional package we don't install.
4
- [install]
5
- linker = "hoisted"
6
- exact = true
7
-
8
- [test]
9
- preload = ["./bun.test.preload.ts"]
package/e2e/app.spec.ts DELETED
@@ -1,124 +0,0 @@
1
- import { type Page, expect, test } from '@playwright/test';
2
-
3
- import {
4
- assertResult,
5
- closeModal,
6
- expectTileHighlighted,
7
- expectTileNotHighlighted,
8
- getBoardTile,
9
- getDictionary,
10
- getDictionaryInput,
11
- getDictionaryTitles,
12
- getLoading,
13
- getRackTile,
14
- getResult,
15
- getSettingOption,
16
- getTooltip,
17
- hoverResult,
18
- moveMouseAway,
19
- openSettingsModal,
20
- solve,
21
- typeRack,
22
- visitIndex,
23
- } from './lib';
24
-
25
- test.describe('app', () => {
26
- test('has title', async ({ page }) => {
27
- await visitIndex(page);
28
-
29
- await expect(page).toHaveTitle('Scrabble Solver 2 - Board Solver & Word Finder');
30
- });
31
-
32
- test('has default setting values', async ({ page }) => {
33
- await visitIndex(page);
34
- await openSettingsModal(page);
35
-
36
- await expect(getSettingOption(page, 'Game', 'Scrabble')).toBeChecked();
37
- await expect(getSettingOption(page, 'Language', 'English (US)')).toBeChecked();
38
- await expect(getSettingOption(page, 'Coordinates', 'Original')).toBeChecked();
39
- await expect(getSettingOption(page, 'Input mode', 'Keyboard')).toBeChecked();
40
- await expect(getSettingOption(page, 'Group remaining tiles', 'Do not group')).toBeChecked();
41
- });
42
-
43
- test.describe('full app test', () => {
44
- test('Scrabble - Polish', async ({ page }) => {
45
- await visitIndex(page);
46
- await openSettingsModal(page);
47
- await getSettingOption(page, 'Language', 'Polski').check();
48
- await closeModal(page);
49
- await typeRack(page, 'abł');
50
- await solve(page);
51
-
52
- await assertResult(page, 0, 'bał', 14);
53
- const definitionsResponse = waitForDictionaryResponse(page);
54
- await hoverResult(page, 0);
55
- await expect(getLoading(page)).toBeVisible();
56
- await definitionsResponse;
57
- await expectTileHighlighted(getRackTile(page, 0));
58
- await expectTileHighlighted(getRackTile(page, 1));
59
- await expectTileHighlighted(getRackTile(page, 2));
60
- await expect(getBoardTile(page, 5, 7)).toHaveValue('b');
61
- await expect(getBoardTile(page, 6, 7)).toHaveValue('a');
62
- await expect(getBoardTile(page, 7, 7)).toHaveValue('ł');
63
- await expectTileHighlighted(getBoardTile(page, 5, 7));
64
- await expectTileHighlighted(getBoardTile(page, 6, 7));
65
- await expectTileHighlighted(getBoardTile(page, 7, 7));
66
- await expect(getDictionaryInput(page)).toHaveValue('bał');
67
- await expect(getLoading(page)).toHaveCount(0);
68
- await expect(getTooltip(page)).toBeVisible();
69
- await moveMouseAway(page);
70
- await expect(getTooltip(page)).toHaveCount(0);
71
- await expectTileNotHighlighted(getRackTile(page, 0));
72
- await expect(getDictionaryTitles(page)).toHaveCount(1);
73
- await expect(getDictionaryTitles(page)).toHaveText('bał');
74
- await expect(getDictionary(page)).toContainText('bać się');
75
- await expect(getDictionary(page)).toContainText('odczuwać lęk, strach');
76
- await expect(getDictionary(page)).toContainText('być niespokojnym o kogoś lub o coś');
77
- await expect(getDictionary(page)).toContainText('nie śmieć, nie odważać się na coś');
78
-
79
- await page.getByLabel('Punkty', { exact: true }).click();
80
- await expect(getTooltip(page)).toBeVisible();
81
- await expect(getTooltip(page)).toHaveText('Punkty');
82
- await assertResult(page, 0, 'ba', 8);
83
- const nextDefinitionsResponse = waitForDictionaryResponse(page);
84
- await hoverResult(page, 0);
85
- await expectTileHighlighted(getRackTile(page, 0));
86
- await expectTileHighlighted(getRackTile(page, 1));
87
- await expectTileNotHighlighted(getRackTile(page, 2));
88
- await expect(getBoardTile(page, 5, 7)).toHaveValue('');
89
- await expect(getBoardTile(page, 6, 7)).toHaveValue('b');
90
- await expect(getBoardTile(page, 7, 7)).toHaveValue('a');
91
- await expectTileNotHighlighted(getBoardTile(page, 5, 7));
92
- await expectTileHighlighted(getBoardTile(page, 6, 7));
93
- await expectTileHighlighted(getBoardTile(page, 7, 7));
94
- await expect(getDictionaryInput(page)).toHaveValue('ba');
95
- await expect(getLoading(page)).toBeVisible();
96
- await nextDefinitionsResponse;
97
- await expect(getLoading(page)).toHaveCount(0);
98
- await expect(getDictionaryTitles(page)).toHaveCount(1);
99
- await expect(getDictionaryTitles(page)).toHaveText('ba');
100
- await expect(getDictionary(page)).toContainText('wykrzyknik, który wyraża głównie podziw, zdziwienie');
101
- await expect(getDictionary(page)).toContainText(
102
- 'w wierzeniach staroegipskich: dusza ludzka ginąca wraz z ciałem',
103
- );
104
-
105
- await getResult(page, 0).click();
106
- await expectTileNotHighlighted(getRackTile(page, 0));
107
- await expectTileNotHighlighted(getRackTile(page, 1));
108
- await expectTileNotHighlighted(getRackTile(page, 2));
109
- await expect(getRackTile(page, 0)).toHaveValue('');
110
- await expect(getRackTile(page, 1)).toHaveValue('');
111
- await expect(getRackTile(page, 2)).toHaveValue('ł');
112
- await expect(getBoardTile(page, 6, 7)).toHaveValue('b');
113
- await expect(getBoardTile(page, 7, 7)).toHaveValue('a');
114
-
115
- const solveButton = page.getByLabel('Rozwiąż', { exact: true });
116
- await expect(solveButton).toBeVisible();
117
- await expect(solveButton).toBeEnabled();
118
- });
119
- });
120
- });
121
-
122
- function waitForDictionaryResponse(page: Page) {
123
- return page.waitForResponse('**/api/dictionary/*/*');
124
- }
@@ -1,25 +0,0 @@
1
- import { test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/112
7
- */
8
- test.describe('#112 - Scrabble - Character bonus not applied', () => {
9
- test('correctly shows points for a result which gets a character bonus', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
-
12
- await Lib.openSettingsModal(page);
13
- await Lib.getSettingOption(page, 'Language', 'Français').check();
14
- await Lib.closeModal(page);
15
- await Lib.typeRack(page, 'jours');
16
- await Lib.solve(page);
17
-
18
- await Lib.assertResult(page, 0, 'jours', 40);
19
- await Lib.assertResult(page, 1, 'jours', 40);
20
- await Lib.assertResult(page, 2, 'jours', 26);
21
- await Lib.assertResult(page, 3, 'jours', 26);
22
- await Lib.assertResult(page, 4, 'jours', 24);
23
- await Lib.assertResult(page, 5, 'jours', 24);
24
- });
25
- });
@@ -1,17 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/129
7
- */
8
- test.describe('#129 - Esc does not close the sidebar when letters input is focused', () => {
9
- test('Esc key closes the sidebar when rack is focused', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.typeRack(page, 'a');
13
- await page.keyboard.press('Escape');
14
-
15
- await expect(Lib.getModal(page)).toHaveCount(0);
16
- });
17
- });
@@ -1,19 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/2
7
- */
8
- test.describe('#2 - "Q" tile does not work', () => {
9
- test('accepts "Q" tile in English', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.typeBoard(page, 'i', 'horizontal', { x: 7, y: 7 });
12
- await Lib.typeRack(page, 'q');
13
- await Lib.solve(page);
14
-
15
- await expect(Lib.getResults(page)).toHaveCount(2);
16
- await Lib.assertResult(page, 0, 'qi', 11);
17
- await Lib.assertResult(page, 1, 'qi', 11);
18
- });
19
- });
@@ -1,19 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/3
7
- */
8
- test.describe('#3 - "X" tile is allowed in Polish', () => {
9
- test('does not accept "X" tile in Polish', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.getSettingOption(page, 'Language', 'Polski').check();
13
- await Lib.closeModal(page);
14
- await Lib.typeBoard(page, 'x', 'horizontal', { x: 7, y: 7 });
15
- await Lib.typeRack(page, 'x');
16
-
17
- await expect(page.getByText('x', { exact: true })).toHaveCount(0);
18
- });
19
- });
@@ -1,25 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/30
7
- */
8
- test.describe('#30 - Clicking inside an icon of non-first unchecked radio button does not select that option', () => {
9
- test('Clicking inside an icon of first unchecked radio button selects it', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.getSettingOption(page, 'Language', 'English (GB)').click();
13
-
14
- const icon = Lib.getSettingOption(page, 'Language', 'English (US)')
15
- .locator('..')
16
- .getByRole('img', { includeHidden: true })
17
- .first();
18
-
19
- // force - the icon is fully covered by the radio's invisible input overlay,
20
- // which is exactly what a click at the icon's coordinates is meant to hit
21
- await icon.click({ force: true });
22
-
23
- await expect(Lib.getSettingOption(page, 'Language', 'English (US)')).toBeChecked();
24
- });
25
- });
@@ -1,18 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/333
7
- */
8
- test.describe('#333 - Disabled radio button has cursor: pointer', () => {
9
- test('Disabled radio button has cursor: not-allowed', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.getSettingOption(page, 'Language', 'Polski').check();
13
- await Lib.getSettingOption(page, 'Gra', 'Scrabble').scrollIntoViewIfNeeded();
14
-
15
- await expect(Lib.getSettingOption(page, 'Gra', 'Super Scrabble')).toBeDisabled();
16
- await expect(Lib.getSettingOption(page, 'Gra', 'Super Scrabble')).toHaveCSS('cursor', 'not-allowed');
17
- });
18
- });
@@ -1,30 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/342
7
- */
8
- test.describe('#342 - Incorrect sorting when filtering', () => {
9
- test('correctly sorts valid results first when text filter is active', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.getSettingOption(page, 'Language', 'Polski').check();
13
- await Lib.closeModal(page);
14
- await Lib.typeBoard(page, 'bopie', 'horizontal', { x: 6, y: 3 });
15
- await Lib.typeBoard(page, 'apu', 'vertical', { x: 8, y: 4 });
16
- await Lib.typeBoard(page, 'o', 'vertical', { x: 10, y: 4 });
17
- await Lib.typeRack(page, 'oe');
18
- await Lib.solve(page);
19
- await Lib.getResultsFilterInput(page).pressSequentially('p');
20
-
21
- await Lib.assertResult(page, 0, 'po', 5);
22
- await Lib.assertResult(page, 1, 'pe', 5);
23
- await Lib.assertResult(page, 2, 'op', 3);
24
- await Lib.assertResult(page, 3, 'eo', 6);
25
- await expect(Lib.getResult(page, 0)).not.toHaveAttribute('aria-hidden');
26
- await expect(Lib.getResult(page, 1)).not.toHaveAttribute('aria-hidden');
27
- await expect(Lib.getResult(page, 2)).not.toHaveAttribute('aria-hidden');
28
- await expect(Lib.getResult(page, 3)).toHaveAttribute('aria-hidden', 'true');
29
- });
30
- });
@@ -1,27 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/352
7
- */
8
- test.describe('#352 - Race condition: dictionary result shows up after clearing the board', () => {
9
- test('Clearing the board prevents pending dictionary results from showing up', async ({ page }) => {
10
- await page.route('**/api/dictionary/en-US/123', async (route) => {
11
- await new Promise((resolve) => setTimeout(resolve, 100));
12
- await route.fulfill({ body: '' });
13
- });
14
-
15
- await Lib.visitIndex(page);
16
-
17
- const dictionaryResponse = page.waitForResponse('**/api/dictionary/en-US/123');
18
- await Lib.getDictionaryInput(page).pressSequentially('123');
19
- await Lib.getDictionaryInput(page).press('Enter');
20
- await page.getByLabel('Clear', { exact: true }).click();
21
- await dictionaryResponse;
22
-
23
- await expect(Lib.getDictionary(page)).not.toContainText('This word is not allowed');
24
- await expect(Lib.getDictionary(page)).not.toContainText('Unexpected end of JSON input');
25
- await expect(Lib.getDictionary(page)).toContainText('Word definition will be shown here.');
26
- });
27
- });
@@ -1,27 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/363
7
- */
8
- test.describe('#363 - Dictionary input should display unique words', () => {
9
- test('highlighting a word should cause only unique new words to be looked up in the dictionary', async ({ page }) => {
10
- await page.route('**/api/dictionary/tr-TR/*', async (route) => {
11
- await new Promise((resolve) => setTimeout(resolve, 100));
12
- await route.fulfill({ body: '' });
13
- });
14
-
15
- await Lib.visitIndex(page);
16
- await Lib.openSettingsModal(page);
17
- await Lib.getSettingOption(page, 'Language', 'Türkçe').check();
18
- await Lib.closeModal(page);
19
- await Lib.typeBoard(page, 'ponje', 'horizontal', { x: 3, y: 7 });
20
- await Lib.typeRack(page, 'er');
21
- await Lib.solve(page);
22
-
23
- await Lib.getResult(page, 0).hover();
24
-
25
- await expect(Lib.getDictionaryInput(page)).toHaveValue('er, je');
26
- });
27
- });
@@ -1,55 +0,0 @@
1
- import { type Page, expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/380
7
- */
8
- test.describe('#380 - No transliteration when pasting', () => {
9
- test('should transliterate special characters when pasting in Romanian', async ({ page }) => {
10
- await testTransliterationOnPaste(page, {
11
- language: 'Română',
12
- word: 'abordaserăți',
13
- transliterated: 'abordaserati',
14
- });
15
- });
16
-
17
- test('should transliterate special characters (except "ñ") when pasting in Spanish', async ({ page }) => {
18
- await testTransliterationOnPaste(page, {
19
- language: 'Español',
20
- word: 'bañó',
21
- transliterated: 'baño',
22
- });
23
- });
24
-
25
- test('should transliterate special characters when pasting in French', async ({ page }) => {
26
- await testTransliterationOnPaste(page, {
27
- language: 'Français',
28
- word: 'pâtisserie',
29
- transliterated: 'patisserie',
30
- });
31
- });
32
- });
33
-
34
- async function testTransliterationOnPaste(
35
- page: Page,
36
- {
37
- language,
38
- word,
39
- transliterated,
40
- }: {
41
- language: string;
42
- word: string;
43
- transliterated: string;
44
- },
45
- ) {
46
- await Lib.visitIndex(page);
47
- await Lib.openSettingsModal(page);
48
- await Lib.getSettingOption(page, 'Language', language).check();
49
- await Lib.closeModal(page);
50
- await Lib.paste(Lib.getBoardTile(page, 0, 0), word);
51
-
52
- for (const [x, value] of Array.from(transliterated).entries()) {
53
- await expect(Lib.getBoardTile(page, x, 0)).toHaveValue(value);
54
- }
55
- }
@@ -1,23 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/398
7
- */
8
- test.describe('#398 - Solitary digraph tiles are recognised as created words', () => {
9
- test('should transliterate special characters when pasting in French', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.openSettingsModal(page);
12
- await Lib.getSettingOption(page, 'Language', 'Español').check();
13
- await Lib.closeModal(page);
14
- await Lib.pasteBoard(page, 'chiclean', 'horizontal', { x: 3, y: 7 });
15
- await Lib.openModal(page, page.getByLabel('Palabras creadas', { exact: true }));
16
-
17
- const modal = Lib.getOpenModal(page);
18
- await expect(modal.getByText('ch', { exact: true })).toHaveCount(0);
19
- await expect(modal.getByText('chiclean', { exact: true })).toBeVisible();
20
- await expect(modal.getByLabel('Incorrecto', { exact: true })).toHaveCount(0);
21
- await expect(modal.getByLabel('Correcto', { exact: true })).toHaveCount(1);
22
- });
23
- });
@@ -1,24 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- const SUPER_SCRABBLE_BOARD_WIDTH = 21;
6
-
7
- /*
8
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/401
9
- */
10
- test.describe('#401 - Board state not preserved on game type change', () => {
11
- test('preserves tiles centered on the board when switching to a larger game', async ({ page }) => {
12
- await Lib.visitIndex(page);
13
- await Lib.typeBoard(page, 'word', 'horizontal', { x: 7, y: 7 });
14
-
15
- await Lib.openSettingsModal(page);
16
- await Lib.getSettingOption(page, 'Game', 'Super Scrabble').check();
17
- await Lib.closeModal(page);
18
-
19
- await expect(Lib.getBoardTile(page, 10, 10, SUPER_SCRABBLE_BOARD_WIDTH)).toHaveValue('w');
20
- await expect(Lib.getBoardTile(page, 11, 10, SUPER_SCRABBLE_BOARD_WIDTH)).toHaveValue('o');
21
- await expect(Lib.getBoardTile(page, 12, 10, SUPER_SCRABBLE_BOARD_WIDTH)).toHaveValue('r');
22
- await expect(Lib.getBoardTile(page, 13, 10, SUPER_SCRABBLE_BOARD_WIDTH)).toHaveValue('d');
23
- });
24
- });
@@ -1,38 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /**
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/445
7
- */
8
- test.describe('#445 - Text selection not disabled in tiles', () => {
9
- test('Tile input declares user-select: none and a transparent selection highlight', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- const inputClass = await Lib.getBoardTile(page, 0, 0).getAttribute('class');
12
-
13
- await expect(Lib.getRackTile(page, 0)).toHaveClass(inputClass ?? '');
14
-
15
- const declarations = await page.evaluate((selector) => {
16
- const styleBySelector = new Map<string, CSSStyleDeclaration>();
17
-
18
- for (const sheet of Array.from(document.styleSheets)) {
19
- for (const rule of Array.from(sheet.cssRules)) {
20
- if (rule instanceof CSSStyleRule) {
21
- for (const selectorPart of rule.selectorText.split(',')) {
22
- styleBySelector.set(selectorPart.trim(), rule.style);
23
- }
24
- }
25
- }
26
- }
27
-
28
- return {
29
- userSelect: styleBySelector.get(selector)?.userSelect,
30
- selectionBackground: styleBySelector.get(`${selector}::selection`)?.backgroundColor,
31
- };
32
- }, `.${inputClass}`);
33
-
34
- expect(declarations.userSelect).toBe('none');
35
- // The production build minifies "transparent" to "rgba(0, 0, 0, 0)".
36
- expect(['transparent', 'rgba(0, 0, 0, 0)']).toContain(declarations.selectionBackground);
37
- });
38
- });
@@ -1,35 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /**
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/451
7
- */
8
- test.describe('#451 - Stale highlight below a short results list', () => {
9
- test('clears the highlight when the pointer moves into the gap below the last result', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.typeRack(page, 'qi');
12
- await Lib.solve(page);
13
-
14
- const results = Lib.getResults(page);
15
- await expect(results.first()).toBeVisible();
16
- await Lib.hoverResult(page, (await results.count()) - 1);
17
-
18
- const highlightedTiles = page.locator('[role="mark"]');
19
- await expect(highlightedTiles.first()).toBeVisible();
20
-
21
- const list = Lib.getResultsContainer(page).locator('[role="list"]');
22
- const listBox = await list.boundingBox();
23
- const listContainerBox = await list.locator('..').boundingBox();
24
-
25
- if (!listBox || !listContainerBox) {
26
- throw new Error('Expected the results list and its container to be visible');
27
- }
28
-
29
- const gapY = listBox.y + listBox.height + 20;
30
- expect(gapY).toBeLessThan(listContainerBox.y + listContainerBox.height);
31
- await page.mouse.move(listBox.x + listBox.width / 2, gapY);
32
-
33
- await expect(highlightedTiles).toHaveCount(0);
34
- });
35
- });
@@ -1,32 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /**
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/452
7
- */
8
- test.describe('#452 - Stale translations cache', () => {
9
- test('recovers when the cached translations are missing a newly added key', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await page.keyboard.press('Shift');
12
- await page.waitForFunction(() => {
13
- return Object.keys(localStorage).some((storageKey) => storageKey.includes('translations'));
14
- });
15
-
16
- await page.evaluate(() => {
17
- const key = Object.keys(localStorage).find((storageKey) => storageKey.includes('translations'));
18
-
19
- if (!key) {
20
- throw new Error('Translations cache not found');
21
- }
22
-
23
- const stored = JSON.parse(localStorage.getItem(key) as string) as { translations: Record<string, string> };
24
- delete stored.translations['words.validity'];
25
- localStorage.setItem(key, JSON.stringify(stored));
26
- });
27
- await page.reload();
28
-
29
- await Lib.openWordsModal(page);
30
- await expect(Lib.getOpenModal(page).getByRole('button', { name: 'Validity', exact: true })).toBeVisible();
31
- });
32
- });
@@ -1,33 +0,0 @@
1
- import { expect, test } from '@playwright/test';
2
-
3
- import * as Lib from '../lib';
4
-
5
- /*
6
- * @see https://github.com/kamilmielnik/scrabble-solver/issues/461
7
- */
8
- test.describe('#461 - Highlighting ignores regular expressions in filters', () => {
9
- test('highlights the regular expression matches in the words table', async ({ page }) => {
10
- await Lib.visitIndex(page);
11
- await Lib.typeBoard(page, 'cat', 'horizontal', { x: 3, y: 3 });
12
- await Lib.typeBoard(page, 'dog', 'horizontal', { x: 3, y: 5 });
13
- await Lib.openWordsModal(page);
14
-
15
- await Lib.getWordsFilterInput(page).pressSequentially('^d|t$');
16
-
17
- await Lib.assertWord(page, 0, 'cat');
18
- await Lib.assertWord(page, 1, 'dog');
19
- await expect(Lib.getWord(page, 0).locator('mark')).toHaveText('t');
20
- await expect(Lib.getWord(page, 1).locator('mark')).toHaveText('d');
21
- });
22
-
23
- test('highlights the regular expression matches in the results table', async ({ page }) => {
24
- await Lib.visitIndex(page);
25
- await Lib.typeBoard(page, 'i', 'horizontal', { x: 7, y: 7 });
26
- await Lib.typeRack(page, 'q');
27
- await Lib.solve(page);
28
-
29
- await Lib.getResultsFilterInput(page).pressSequentially('^q');
30
-
31
- await expect(Lib.getResult(page, 0).locator('mark')).toHaveText('q');
32
- });
33
- });