Skip to content

Commit

Permalink
Add weekly achievement
Browse files Browse the repository at this point in the history
  • Loading branch information
ajuvonen committed Sep 22, 2024
1 parent b7df13a commit 5aed127
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 80 deletions.
22 changes: 22 additions & 0 deletions e2e/log.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,25 @@ test('achievement notifications work', async ({page}) => {
await page.getByTestId('dialog-close-button').click();
await expect(page.getByTestId('dialog')).toBeHidden();
});

test('weekly achievement works', async ({page}) => {
await page.goto('/');
await page.getByTestId('home-start-button').click();
await page.getByTestId('veggie-search-button').click();
const fruits = (
await page.getByTestId('veggie-search-group-Fruit').locator('.veggie-search__option').all()
).slice(0, 10);
const vegetables = (
await page.getByTestId('veggie-search-group-Vegetable').locator('.veggie-search__option').all()
).slice(0, 10);
const grains = (
await page.getByTestId('veggie-search-group-Grain').locator('.veggie-search__option').all()
).slice(0, 10);
for (const element of fruits.concat(vegetables, grains)) {
await element.click();
}
await expect(page.getByTestId('dialog')).toBeVisible();
await expect(page.getByTestId('badge-thirtyVeggies-3')).toBeVisible();
await page.getByTestId('dialog-close-button').click();
await expect(page.getByTestId('dialog')).toBeHidden();
});
6 changes: 6 additions & 0 deletions src/components/AchievementBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ const achievementProps: AchievementProps = {
emoji: CATEGORY_EMOJI.Vegetable,
},
},
thirtyVeggies: {
[AchievementLevel.Gold]: {
textProps: [],
emoji: '🎉',
},
},
};
</script>
<template>
Expand Down
10 changes: 10 additions & 0 deletions src/components/AchievementList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ const {achievements} = storeToRefs(useAppStateStore());
</script>
<template>
<div class="achievement-list">
<div>
<p class="achievement-list__label">{{ $t('achievements.thirtyVeggies.title') }}</p>
<div class="achievement-list__badge-container">
<AchievementBadge
:active="achievements.thirtyVeggies >= AchievementLevel.Gold"
:level="AchievementLevel.Gold"
achievement="thirtyVeggies"
/>
</div>
</div>
<div>
<p class="achievement-list__label">{{ $t('achievements.completionist.title') }}</p>
<div class="achievement-list__badge-container">
Expand Down
2 changes: 1 addition & 1 deletion src/components/__tests__/AchievementList.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('AchievementList', () => {
it('renders', () => {
const wrapper = mount(AchievementList);
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.findAll('.badge[aria-disabled="true"]').length).toBe(18);
expect(wrapper.findAll('.badge[aria-disabled="true"]').length).toBe(19);
});

it('renders with badges enabled', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

exports[`AchievementList > renders 1`] = `
"<div data-v-1c120264="" class="achievement-list">
<div data-v-1c120264="">
<p data-v-1c120264="" class="achievement-list__label">This Week</p>
<div data-v-1c120264="" class="achievement-list__badge-container">
<div data-v-06381b3c="" data-v-1c120264="" aria-disabled="true" title="30 unique veggies" aria-label="30 unique veggies" data-test-id="badge-thirtyVeggies-3" class="badge" role="img">
<div data-v-06381b3c="" class="badge__background badge__background--Gold" aria-hidden="true">
<div data-v-06381b3c="" class="badge__emoji">🎉</div>
</div>
<div data-v-06381b3c="" aria-hidden="true" class="badge__text">30 Weekly</div>
</div>
</div>
</div>
<div data-v-1c120264="">
<p data-v-1c120264="" class="achievement-list__label">The Completionist</p>
<div data-v-1c120264="" class="achievement-list__badge-container">
Expand Down Expand Up @@ -140,6 +151,17 @@ exports[`AchievementList > renders 1`] = `

exports[`AchievementList > renders with badges enabled 1`] = `
"<div data-v-1c120264="" class="achievement-list">
<div data-v-1c120264="">
<p data-v-1c120264="" class="achievement-list__label">This Week</p>
<div data-v-1c120264="" class="achievement-list__badge-container">
<div data-v-06381b3c="" data-v-1c120264="" aria-disabled="false" title="30 unique veggies" aria-label="30 unique veggies" data-test-id="badge-thirtyVeggies-3" class="badge" role="img">
<div data-v-06381b3c="" class="badge__background badge__background--Gold" aria-hidden="true">
<div data-v-06381b3c="" class="badge__emoji">🎉</div>
</div>
<div data-v-06381b3c="" aria-hidden="true" class="badge__text">30 Weekly</div>
</div>
</div>
</div>
<div data-v-1c120264="">
<p data-v-1c120264="" class="achievement-list__label">The Completionist</p>
<div data-v-1c120264="" class="achievement-list__badge-container">
Expand Down
102 changes: 60 additions & 42 deletions src/components/__tests__/achievements.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const defaultAchievements: Achievements = {
experimenterLeafy: AchievementLevel.NoAchievement,
experimenterRoot: AchievementLevel.NoAchievement,
experimenterVegetable: AchievementLevel.NoAchievement,
thirtyVeggies: AchievementLevel.NoAchievement,
};

describe('achievements', () => {
Expand All @@ -37,132 +38,149 @@ describe('achievements', () => {

it('advances completionist', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([...Array(39)], 0, 0, 0);
advanceAchievements(0, [...Array(39)], 0, 0, 0);
expect(achievements.value.completionist).toEqual(AchievementLevel.NoAchievement);
advanceAchievements([...Array(40)], 0, 0, 0);
advanceAchievements(0, [...Array(40)], 0, 0, 0);
expect(achievements.value.completionist).toBe(AchievementLevel.Bronze);
advanceAchievements([...Array(80)], 0, 0, 0);
advanceAchievements(0, [...Array(80)], 0, 0, 0);
expect(achievements.value.completionist).toBe(AchievementLevel.Silver);
advanceAchievements([...Array(150)], 0, 0, 0);
advanceAchievements(0, [...Array(150)], 0, 0, 0);
expect(achievements.value.completionist).toBe(AchievementLevel.Gold);
});

it('goes straight to silver', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([...Array(80)], 0, 0, 0);
advanceAchievements(0, [...Array(80)], 0, 0, 0);
expect(achievements.value.completionist).toBe(AchievementLevel.Silver);
});

it('goes straight to gold', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([...Array(150)], 0, 0, 0);
advanceAchievements(0, [...Array(150)], 0, 0, 0);
expect(achievements.value.completionist).toBe(AchievementLevel.Gold);
});

it('advances experimenterFruit', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(FRUITS, 14), 0, 0, 0);
advanceAchievements(0, take(FRUITS, 14), 0, 0, 0);
expect(achievements.value.experimenterFruit).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(FRUITS, 15), 0, 0, 0);
advanceAchievements(0, take(FRUITS, 15), 0, 0, 0);
expect(achievements.value.experimenterFruit).toBe(AchievementLevel.Gold);
});

it('goes straight to gold', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(FRUITS, 15), 0, 0, 0);
advanceAchievements(0, take(FRUITS, 15), 0, 0, 0);
expect(achievements.value.experimenterFruit).toBe(AchievementLevel.Gold);
});

it('advances experimenterVegetable', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(VEGETABLES, 14), 0, 0, 0);
advanceAchievements(0, take(VEGETABLES, 14), 0, 0, 0);
expect(achievements.value.experimenterVegetable).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(VEGETABLES, 15), 0, 0, 0);
advanceAchievements(0, take(VEGETABLES, 15), 0, 0, 0);
expect(achievements.value.experimenterVegetable).toBe(AchievementLevel.Gold);
});

it('advances experimenterLeafy', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(LEAFIES, 14), 0, 0, 0);
advanceAchievements(0, take(LEAFIES, 14), 0, 0, 0);
expect(achievements.value.experimenterLeafy).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(LEAFIES, 15), 0, 0, 0);
advanceAchievements(0, take(LEAFIES, 15), 0, 0, 0);
expect(achievements.value.experimenterLeafy).toBe(AchievementLevel.Gold);
});

it('advances experimenterBean', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(BEANS, 14), 0, 0, 0);
advanceAchievements(0, take(BEANS, 14), 0, 0, 0);
expect(achievements.value.experimenterBean).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(BEANS, 15), 0, 0, 0);
advanceAchievements(0, take(BEANS, 15), 0, 0, 0);
expect(achievements.value.experimenterBean).toBe(AchievementLevel.Gold);
});

it('advances experimenterRoot', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(ROOTS, 14), 0, 0, 0);
advanceAchievements(0, take(ROOTS, 14), 0, 0, 0);
expect(achievements.value.experimenterRoot).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(ROOTS, 15), 0, 0, 0);
advanceAchievements(0, take(ROOTS, 15), 0, 0, 0);
expect(achievements.value.experimenterRoot).toBe(AchievementLevel.Gold);
});

it('advances experimenterGrain', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(take(GRAINS, 14), 0, 0, 0);
advanceAchievements(0, take(GRAINS, 14), 0, 0, 0);
expect(achievements.value.experimenterGrain).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(take(GRAINS, 15), 0, 0, 0);
advanceAchievements(0, take(GRAINS, 15), 0, 0, 0);
expect(achievements.value.experimenterGrain).toBe(AchievementLevel.Gold);
});

it('advances hot streak', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([], 4, 0, 0);
advanceAchievements(0, [], 4, 0, 0);
expect(achievements.value.hotStreak).toEqual(AchievementLevel.NoAchievement);
advanceAchievements([], 5, 0, 0);
advanceAchievements(0, [], 5, 0, 0);
expect(achievements.value.hotStreak).toEqual(AchievementLevel.Bronze);
advanceAchievements([], 10, 0, 0);
advanceAchievements(0, [], 10, 0, 0);
expect(achievements.value.hotStreak).toEqual(AchievementLevel.Silver);
advanceAchievements([], 20, 0, 0);
advanceAchievements(0, [], 20, 0, 0);
expect(achievements.value.hotStreak).toEqual(AchievementLevel.Gold);
});

it('advances committed', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([], 0, 11, 0);
advanceAchievements(0, [], 0, 11, 0);
expect(achievements.value.committed).toEqual(AchievementLevel.NoAchievement);
advanceAchievements([], 0, 12, 0);
advanceAchievements(0, [], 0, 12, 0);
expect(achievements.value.committed).toEqual(AchievementLevel.Bronze);
advanceAchievements([], 0, 26, 0);
advanceAchievements(0, [], 0, 26, 0);
expect(achievements.value.committed).toEqual(AchievementLevel.Silver);
advanceAchievements([], 0, 52, 0);
advanceAchievements(0, [], 0, 52, 0);
expect(achievements.value.committed).toEqual(AchievementLevel.Gold);
});

it('advances challengeAccepted', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements([], 0, 0, 4);
advanceAchievements(0, [], 0, 0, 4);
expect(achievements.value.challengeAccepted).toEqual(AchievementLevel.NoAchievement);
advanceAchievements([], 0, 0, 5);
advanceAchievements(0, [], 0, 0, 5);
expect(achievements.value.challengeAccepted).toEqual(AchievementLevel.Bronze);
advanceAchievements([], 0, 0, 10);
advanceAchievements(0, [], 0, 0, 10);
expect(achievements.value.challengeAccepted).toEqual(AchievementLevel.Silver);
advanceAchievements([], 0, 0, 20);
advanceAchievements(0, [], 0, 0, 20);
expect(achievements.value.challengeAccepted).toEqual(AchievementLevel.Gold);
});

it('advances thirtyVeggies', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(29, [], 0, 0, 0);
expect(achievements.value.thirtyVeggies).toEqual(AchievementLevel.NoAchievement);
advanceAchievements(30, [], 0, 0, 0);
expect(achievements.value.thirtyVeggies).toBe(AchievementLevel.Gold);
});

it('resets thirtyVeggies after a week', async () => {
const {advanceAchievements, achievements} = await withSetup(useAchievements);
advanceAchievements(30, [], 0, 0, 0);
expect(achievements.value.thirtyVeggies).toEqual(AchievementLevel.Gold);
advanceAchievements(0, [], 0, 0, 0);
expect(achievements.value.thirtyVeggies).toBe(AchievementLevel.NoAchievement);
});

it('resets achievements', async () => {
const {advanceAchievements, achievements, resetAchievements} = await withSetup(useAchievements);
advanceAchievements(ALL_VEGGIES, 30, 52, 20);
advanceAchievements(30, ALL_VEGGIES, 30, 52, 20);
expect(achievements.value).toEqual({
challengeAccepted: 3,
committed: 3,
completionist: 3,
experimenterBean: 3,
experimenterRoot: 3,
experimenterFruit: 3,
experimenterGrain: 3,
experimenterLeafy: 3,
experimenterVegetable: 3,
hotStreak: 3,
challengeAccepted: AchievementLevel.Gold,
committed: AchievementLevel.Gold,
completionist: AchievementLevel.Gold,
experimenterBean: AchievementLevel.Gold,
experimenterRoot: AchievementLevel.Gold,
experimenterFruit: AchievementLevel.Gold,
experimenterGrain: AchievementLevel.Gold,
experimenterLeafy: AchievementLevel.Gold,
experimenterVegetable: AchievementLevel.Gold,
hotStreak: AchievementLevel.Gold,
thirtyVeggies: AchievementLevel.Gold,
});
resetAchievements();
expect(achievements.value).toEqual(defaultAchievements);
Expand Down
54 changes: 29 additions & 25 deletions src/hooks/achievements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {Achievements} from '@/utils/types';

type AdvanceEvent = {
type: 'ADVANCE';
veggiesThisWeek: number;
uniqueVeggies: string[];
completedChallenges: number;
hotStreakLength: number;
Expand Down Expand Up @@ -38,6 +39,10 @@ const guards = {
(targetGroup: string[]) =>
({event}: GuardArgs<MachineContext, AdvanceEvent>) =>
intersection(event.uniqueVeggies, targetGroup).length >= 15,
thirtyVeggies:
(toGold: boolean) =>
({event}: GuardArgs<MachineContext, AdvanceEvent>) =>
toGold ? event.veggiesThisWeek >= 30 : event.veggiesThisWeek < 30,
};

export function useAchievements() {
Expand All @@ -52,31 +57,7 @@ export function useAchievements() {
on: {
RESET: [
{
target: '.challengeAccepted.0',
},
{
target: `.committed.0`,
},
{
target: `.completionist.0`,
},
{
target: `.hotStreak.0`,
},
{
target: `.experimenterFruit.0`,
},
{
target: `.experimenterVegetable.0`,
},
{
target: `.experimenterLeafy.0`,
},
{
target: `.experimenterBean.0`,
},
{
target: `.experimenterGrain.0`,
target: '.',
},
],
},
Expand Down Expand Up @@ -349,6 +330,27 @@ export function useAchievements() {
'3': {},
},
},
thirtyVeggies: {
initial: '0',
states: {
'0': {
on: {
ADVANCE: {
target: '3',
guard: guards.thirtyVeggies(true),
},
},
},
'3': {
on: {
ADVANCE: {
target: '0',
guard: guards.thirtyVeggies(false),
},
},
},
},
},
},
}),
).start();
Expand All @@ -370,13 +372,15 @@ export function useAchievements() {
return {
achievements,
advanceAchievements: (
veggiesThisWeek: number,
uniqueVeggies: string[],
hotStreakLength: number,
totalWeeks: number,
completedChallenges: number,
) =>
actor.send({
type: 'ADVANCE',
veggiesThisWeek,
uniqueVeggies,
hotStreakLength,
totalWeeks,
Expand Down
Loading

0 comments on commit 5aed127

Please sign in to comment.