Skip to content

Commit e117e36

Browse files
committed
refactor: establish one rendering mechanism for all custom elements
Replace the custom rendering code in all custom elements with one standard lit-html-powered rendering mechanism. The mechanism is governed by the new BaseElement custom element from which all other elements are extended. Announce score changes via polite ARIA live region. Make use of customizable select elements and style them. Add suit icons to suit selects. Add seal color orbs to seal selects. Add "Move left" and "Move right" buttons to jokers and playing cards for re-arranging them in addition to the existing drag-and-drop functionality.
1 parent b5a199d commit e117e36

18 files changed

Lines changed: 1699 additions & 1089 deletions

index.html

Lines changed: 74 additions & 279 deletions
Large diffs are not rendered by default.

playwright/tests/calculator.test.ts

Lines changed: 192 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ test.describe('Jokers', () => {
131131
await page.getByRole('button', { name: /^Add joker$/ }).click()
132132
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-25*****0*0*1*-')
133133

134-
const name = page.locator('[name="joker-name-0"]')
134+
const name = page.locator('[name^="joker-name-"]')
135135
const nameButton = name.getByRole('button', { name: /^Show joker options$/ })
136136
await nameButton.click()
137137
const nameInput = name.getByRole('combobox', { name: /^Filter jokers$/ })
@@ -149,13 +149,182 @@ test.describe('Jokers', () => {
149149
await count.blur()
150150
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-14*****0*0*1*2-')
151151

152-
const edition = page.getByRole('combobox', { name: /^Edition$/ })
153-
await edition.selectOption('Foil')
152+
await page.getByRole('combobox', { name: /^Edition$/ }).selectOption('Foil')
154153
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-14*1****0*0*1*2-')
155154
})
155+
156+
test('show/hide dependent fields when switching joker', async ({ page }) => {
157+
await page.goto('/')
158+
159+
await page.getByRole('button', { name: /^Add joker$/ }).click()
160+
const jokerCard = page.locator('joker-card')
161+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeHidden()
162+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeHidden()
163+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeHidden()
164+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeHidden()
165+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeHidden()
166+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeHidden()
167+
168+
const name = jokerCard.locator('[name^="joker-name-"]')
169+
const nameButton = name.getByRole('button', { name: /^Show joker options$/ })
170+
await nameButton.click()
171+
await name.getByRole('option', { name: /^The Idol$/ }).click()
172+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeVisible()
173+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeVisible()
174+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeHidden()
175+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeHidden()
176+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeHidden()
177+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeHidden()
178+
179+
await nameButton.click()
180+
await name.getByRole('option', { name: /^Blue Joker$/ }).click()
181+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeHidden()
182+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeHidden()
183+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeVisible()
184+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeHidden()
185+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeHidden()
186+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeHidden()
187+
188+
await nameButton.click()
189+
await name.getByRole('option', { name: /^Campfire$/ }).click()
190+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeHidden()
191+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeHidden()
192+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeHidden()
193+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeVisible()
194+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeHidden()
195+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeHidden()
196+
197+
await nameButton.click()
198+
await name.getByRole('option', { name: /^Card Sharp$/ }).click()
199+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeHidden()
200+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeHidden()
201+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeHidden()
202+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeHidden()
203+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeVisible()
204+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeHidden()
205+
206+
await nameButton.click()
207+
await name.getByRole('option', { name: /^Ceremonial Dagger$/ }).click()
208+
await expect(jokerCard.getByRole('combobox', { name: /^Suit$/ })).toBeHidden()
209+
await expect(jokerCard.getByRole('combobox', { name: /^Rank$/ })).toBeHidden()
210+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Chips$/ })).toBeHidden()
211+
await expect(jokerCard.getByRole('spinbutton', { name: /^xMult$/ })).toBeHidden()
212+
await expect(jokerCard.getByRole('checkbox', { name: /^Active\?$/ })).toBeHidden()
213+
await expect(jokerCard.getByRole('spinbutton', { name: /^\+Mult$/ })).toBeVisible()
214+
})
215+
216+
test('can be duplicated', async ({ page }) => {
217+
await page.goto('/?state=----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-14*1****0*0*1*2-')
218+
219+
await page.getByRole('button', { name: /^Duplicate joker$/ }).click()
220+
const dialog = page.getByRole('dialog', { name: /^Duplicate joker\?$/ })
221+
await dialog.getByRole('spinbutton', { name: /^Number of copies$/ }).fill('2')
222+
await dialog.getByRole('button', { name: /^Ok$/ }).click()
223+
const jokerCards = page.locator('joker-card')
224+
await expect(jokerCards).toHaveCount(3)
225+
for (const jokerCard of await jokerCards.all()) {
226+
await expect(jokerCard.getByRole('spinbutton', { name: /^Count$/ })).toHaveValue('2')
227+
await expect(jokerCard.getByRole('combobox', { name: /^Edition$/ })).toHaveValue('Foil')
228+
}
229+
})
230+
231+
test('can be re-arranged with left/right buttons', async ({ page }) => {
232+
await page.goto('/?state=----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-126*****2*3*1*_132*****0*0*1*_69****12.4*0*0*1*-')
233+
234+
const cards = page.locator('joker-card')
235+
await expect(cards.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
236+
await expect(cards.nth(0).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
237+
await expect(cards.nth(0).getByRole('button', { name: /^Move joker left$/ })).toHaveAttribute('disabled')
238+
await expect(cards.nth(0).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
239+
240+
await expect(cards.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
241+
await expect(cards.nth(1).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
242+
await expect(cards.nth(1).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
243+
244+
await expect(cards.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
245+
await expect(cards.nth(2).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
246+
await expect(cards.nth(2).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
247+
await expect(cards.nth(2).getByRole('button', { name: /^Move joker right$/ })).toHaveAttribute('disabled')
248+
await cards.nth(0).getByRole('button', { name: /^Move joker right$/ }).click()
249+
250+
const cards2 = page.locator('joker-card')
251+
await expect(cards2.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
252+
await expect(cards2.nth(0).getByRole('button', { name: /^Move joker left$/ })).toHaveAttribute('disabled')
253+
await expect(cards2.nth(0).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
254+
255+
await expect(cards2.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
256+
await expect(cards2.nth(1).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
257+
await expect(cards2.nth(1).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
258+
await expect(cards2.nth(1).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
259+
260+
await expect(cards2.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
261+
await expect(cards2.nth(2).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
262+
await expect(cards2.nth(2).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
263+
await expect(cards2.nth(2).getByRole('button', { name: /^Move joker right$/ })).toHaveAttribute('disabled')
264+
await expect(cards2.nth(2).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
265+
await expect(cards2.nth(2).getByRole('button', { name: /^Move joker right$/ })).toHaveAttribute('disabled')
266+
await cards2.nth(2).getByRole('button', { name: /^Move joker left$/ }).click()
267+
268+
const cards3 = page.locator('joker-card')
269+
await expect(cards3.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
270+
await expect(cards3.nth(0).getByRole('button', { name: /^Move joker left$/ })).toHaveAttribute('disabled')
271+
await expect(cards3.nth(0).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
272+
273+
await expect(cards3.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
274+
await expect(cards3.nth(1).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
275+
await expect(cards3.nth(1).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
276+
await expect(cards3.nth(1).getByRole('button', { name: /^Move joker right$/ })).not.toHaveAttribute('disabled')
277+
278+
await expect(cards3.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
279+
await expect(cards3.nth(2).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
280+
await expect(cards3.nth(2).getByRole('button', { name: /^Move joker left$/ })).not.toHaveAttribute('disabled')
281+
await expect(cards3.nth(2).getByRole('button', { name: /^Move joker right$/ })).toHaveAttribute('disabled')
282+
})
283+
284+
test('can be drag-and-dropped to re-arrange', async ({ page }) => {
285+
await page.goto('/?state=----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*-126*****2*3*1*_132*****0*0*1*_69****12.4*0*0*1*-')
286+
287+
await expect(page.locator('[name^="joker-name-"]')).toHaveCount(3)
288+
const cards = page.locator('joker-card')
289+
await expect(cards.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
290+
await expect(cards.nth(0).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
291+
await expect(cards.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
292+
await expect(cards.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
293+
await expect(cards.nth(2).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
294+
295+
const target = cards.nth(1)
296+
const targetRect = await target.evaluate((element) => element.getBoundingClientRect())
297+
await cards.nth(0).dragTo(
298+
target,
299+
// Make sure the drop location is safely past the halfway point of the target.
300+
{ targetPosition: { x: targetRect.width * 0.75, y: 0 } },
301+
)
302+
303+
const cards2 = page.locator('joker-card')
304+
await expect(cards2.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
305+
await expect(cards2.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
306+
await expect(cards2.nth(1).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
307+
await expect(cards2.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
308+
await expect(cards2.nth(2).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
309+
310+
const target2 = cards2.nth(0)
311+
const target2Rect = await target2.evaluate((element) => element.getBoundingClientRect())
312+
await cards2.nth(2).dragTo(
313+
target2,
314+
// Make sure the drop location is safely before the halfway point of the target.
315+
{ targetPosition: { x: target2Rect.width * 0.25, y: 0 } },
316+
)
317+
318+
const cards3 = page.locator('joker-card')
319+
await expect(cards3.nth(0).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'Hologram')
320+
await expect(cards3.nth(0).getByRole('spinbutton', { name: /^xMult$/ })).toHaveValue('12.4')
321+
await expect(cards3.nth(1).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Family')
322+
await expect(cards3.nth(2).locator('[name^="joker-name-"]')).toHaveJSProperty('value', 'The Idol')
323+
await expect(cards3.nth(2).getByRole('combobox', { name: /^Rank$/ })).toHaveValue('Queen')
324+
})
156325
})
157326

158-
test.describe('Hand', () => {
327+
test.describe('Playing cards', () => {
159328
test('continuously updates URL with current state', async ({ page }) => {
160329
await page.goto('/')
161330

@@ -169,24 +338,33 @@ test.describe('Hand', () => {
169338
await count.blur()
170339
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--0*0*****1*2')
171340

172-
const rank = page.getByRole('combobox', { name: /^Rank$/ })
173-
await rank.selectOption('10')
341+
await page.getByRole('combobox', { name: /^Rank$/ }).selectOption('10')
174342
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--4*0*****1*2')
175343

176-
const suit = page.getByRole('combobox', { name: /^Suit$/ })
177-
await suit.selectOption('Spades')
344+
await page.getByRole('combobox', { name: /^Suit$/ }).selectOption('Spades')
178345
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--4*1*****1*2')
179346

180-
const edition = page.getByRole('combobox', { name: /^Edition$/ })
181-
await edition.selectOption('Foil')
347+
await page.getByRole('combobox', { name: /^Edition$/ }).selectOption('Foil')
182348
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--4*1*1****1*2')
183349

184-
const enhancement = page.getByRole('combobox', { name: /^Enhancement$/ })
185-
await enhancement.selectOption('Mult')
350+
await page.getByRole('combobox', { name: /^Enhancement$/ }).selectOption('Mult')
186351
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--4*1*1*2***1*2')
187352

188-
const seal = page.getByRole('combobox', { name: /^Seal$/ })
189-
await seal.selectOption('Gold')
353+
await page.getByRole('combobox', { name: /^Seal$/ }).selectOption('Gold')
190354
await expect(page).toHaveURL((url) => url.searchParams.get('state') === '----1--5-___________-*_*_*_*_*_*_*_*_*_*_*_*--4*1*1*2*1**1*2')
191355
})
356+
357+
test('show/hide debuffed checkbox when The Pillar is active', async ({ page }) => {
358+
await page.goto('/')
359+
360+
await page.getByRole('button', { name: /^Add card$/ }).click()
361+
const playingCard = page.locator('playing-card')
362+
await expect(playingCard.getByRole('checkbox', { name: /^Debuffed\?$/ })).toBeHidden()
363+
364+
const blind = page.locator('[name="blindName"]')
365+
const blindButton = blind.getByRole('button', { name: /^Show blind options$/ })
366+
await blindButton.click()
367+
await blind.getByRole('option', { name: /^The Pillar$/ }).click()
368+
await expect(playingCard.getByRole('checkbox', { name: /^Debuffed\?$/ })).toBeVisible()
369+
})
192370
})

src/lib/doBigMath.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ export function doBigMath (scoreValues: ScoreValue[], deck: DeckName) {
4040
}
4141

4242
// Balatro seems to round values starting at a certain threshold and it seems to round down. 🤔
43-
const score = actualScore.greaterThan(10000) ? floor(actualScore) : actualScore
43+
const score = actualScore.greaterThan(10_000) ? floor(actualScore) : actualScore
4444
return { score: score.toString(), log }
4545
}

0 commit comments

Comments
 (0)