Skip to content

Commit

Permalink
Merge pull request #94 from WhyAsh5114/87-prevent-split-structure-cha…
Browse files Browse the repository at this point in the history
…nge-after-mesocycle-has-workouts

Prevent split structure change after mesocycle has workouts
  • Loading branch information
WhyAsh5114 authored Sep 15, 2024
2 parents 4b10d72 + 4f361cb commit 50c79c6
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 34 deletions.
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const config: PlaywrightTestConfig = {
retries: 2,
globalSetup: './tests/global-setup',
globalTeardown: './tests/global-teardown',
workers: process.env.CI ? 1 : '50%',
workers: process.env.CI ? 1 : '25%',
reporter: process.env.CI ? 'list' : 'html',
use: {
baseURL: 'http://localhost:4173',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { MesocycleExerciseTemplateWithoutIdsOrIndex } from '$lib/components/mesocycleAndExerciseSplit/commonTypes';
import type { RouterOutputs } from '$lib/trpc/router';
import type { Prisma } from '@prisma/client';

export type FullMesocycleWithExerciseSplit = Prisma.MesocycleGetPayload<{
Expand All @@ -11,7 +12,7 @@ type MesocycleExerciseSplitDayWithoutIds = Omit<
>;

export function createMesocycleExerciseSplitRunes() {
let mesocycle: FullMesocycleWithExerciseSplit | null = $state(null);
let mesocycle: RouterOutputs['mesocycles']['findById'] = $state(null);
let splitDays: MesocycleExerciseSplitDayWithoutIds[] = $state(
Array.from({ length: 7 }).map(() => ({ name: '', isRestDay: false }))
);
Expand Down Expand Up @@ -137,7 +138,7 @@ export function createMesocycleExerciseSplitRunes() {
saveStoresToLocalStorage();
}

function loadExerciseSplit(mesocycleWithExerciseSplit: FullMesocycleWithExerciseSplit) {
function loadExerciseSplit(mesocycleWithExerciseSplit: NonNullable<RouterOutputs['mesocycles']['findById']>) {
// Same mesocycle, don't reset and load new data, reuse and continue editing
if (mesocycleWithExerciseSplit.id === mesocycle?.id) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,15 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import AddIcon from 'virtual:icons/lucide/plus';
import RemoveIcon from 'virtual:icons/lucide/minus';
import WarningIcon from 'virtual:icons/lucide/triangle-alert';
import LockIcon from 'virtual:icons/lucide/lock';
import { mesocycleExerciseSplitRunes } from '../mesocycleExerciseSplitRunes.svelte';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import { cn } from '$lib/utils';
const difference = <T,>(setA: Set<T>, setB: Set<T>): Set<T> => {
return new Set([...setA].filter((x) => !setB.has(x)));
};
let consistencyLossDays: string[] = $state([]);
let dataLossDays: number[] = $state([]);
let warningDialogOpen = $state(false);
let showWarningIcon = $derived.by(() => {
if (mesocycleExerciseSplitRunes.mesocycle === null) return false;
const mesocycleSplitDayNames = new Set(
mesocycleExerciseSplitRunes.mesocycle?.mesocycleExerciseSplitDays.map((splitDay) => splitDay.name)
);
const currentSplitDayNames = new Set(mesocycleExerciseSplitRunes.splitDays.map((splitDay) => splitDay.name));
const inconsistentDays = difference(mesocycleSplitDayNames, currentSplitDayNames);
consistencyLossDays = Array.from(inconsistentDays);
return inconsistentDays.size > 0;
});
let exerciseSplitLocked = $derived(mesocycleExerciseSplitRunes.mesocycle!.workoutsOfMesocycle.length > 0);
async function submitStructure(warningAcknowledged = false) {
if (!mesocycleExerciseSplitRunes.validateSplitStructure()) {
Expand Down Expand Up @@ -67,20 +52,14 @@
<Table.Head>
<Popover.Root>
<Popover.Trigger
class={cn({ 'text-background': !showWarningIcon })}
aria-label="mesocycle-exercise-split-edit-warning"
disabled={!exerciseSplitLocked}
class={cn({ hidden: !exerciseSplitLocked })}
aria-label="mesocycle-exercise-split-edit-lock"
>
<WarningIcon class="h-4 w-4" />
<LockIcon class="h-4 w-4" />
</Popover.Trigger>
<Popover.Content class="w-60 text-sm" align="end">
Unmatched day names: <span class="font-semibold">
{consistencyLossDays.join(', ')}
</span>
<br />
<p class="text-muted-foreground">
Avoid unnecessarily changing split day names, as this can exclude workouts with unmatched split names
from progression calculations and analytics.
</p>
<Popover.Content class="w-60 text-sm" align="start">
Cannot change the length or rest days of the mesocycle's exercise split after workouts have been added
</Popover.Content>
</Popover.Root>
</Table.Head>
Expand All @@ -104,6 +83,7 @@
<Table.Cell class="p-0 text-center">
<Checkbox
checked={splitDay.isRestDay}
disabled={exerciseSplitLocked}
onCheckedChange={(checked) => {
if (checked === 'indeterminate') return;
mesocycleExerciseSplitRunes.toggleSplitDay(dayNumber, checked);
Expand All @@ -119,13 +99,18 @@
<div class="mb-1 mt-auto grid grid-cols-2 gap-1 pt-1">
<Button
class="gap-2"
disabled={mesocycleExerciseSplitRunes.splitDays.length === 1}
disabled={mesocycleExerciseSplitRunes.splitDays.length === 1 || exerciseSplitLocked}
onclick={mesocycleExerciseSplitRunes.removeSplitDay}
variant="secondary"
>
<RemoveIcon /> Remove
</Button>
<Button class="gap-2" onclick={mesocycleExerciseSplitRunes.addSplitDay} variant="secondary">
<Button
class="gap-2"
onclick={mesocycleExerciseSplitRunes.addSplitDay}
disabled={exerciseSplitLocked}
variant="secondary"
>
<AddIcon /> Add
</Button>
</div>
Expand Down
38 changes: 38 additions & 0 deletions tests/models/mesocycles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,47 @@ test("edit mesocycle's exercise split", async ({ page }) => {
await expect(page.getByRole('main')).toContainText('Face pulls 4 Straight sets of 15 to 30 reps Rear delts');
});

test('disallow exercise split editing after workout added', async ({ page }) => {
await createMesocycle(page, { exerciseSplitCreated: true });
await page.getByRole('link', { name: 'Workouts' }).click();
await page.getByLabel('create-workout').click();
await page.getByPlaceholder('Type here').fill('100');
await page.getByRole('button', { name: 'Next' }).click();
await page.getByTestId('Pull-ups-menu-button').click();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByTestId('Barbell rows-menu-button').click();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByTestId('Dumbbell bicep curls-menu-button').click();
await page.getByRole('menuitem', { name: 'Delete' }).click();

await page.locator('[id="Face\\ pulls-set-1-reps"]').fill('13');
await page.locator('[id="Face\\ pulls-set-2-reps"]').fill('11');
await page.locator('[id="Face\\ pulls-set-3-reps"]').fill('11');
await page.locator('[id="Face\\ pulls-set-1-load"]').fill('10');
await page.getByTestId('Face pulls-set-1-action').click();
await page.getByTestId('Face pulls-set-2-action').click();
await page.getByTestId('Face pulls-set-3-action').click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await page.waitForURL('/workouts');

const lockedText =
"Cannot change the length or rest days of the mesocycle's exercise split after workouts have been added";
await page.getByRole('link', { name: 'Mesocycles' }).click();
await page.getByRole('link', { name: 'MyMeso Active' }).first().click();
await expect(page.getByRole('main')).toContainText(new Date().toLocaleDateString());
await page.getByRole('tab', { name: 'Split' }).click();
await page.getByRole('button', { name: 'Edit' }).click();
await page.getByLabel('mesocycle-exercise-split-edit').click();
await expect(page.getByText(lockedText)).toBeInViewport();
await expect(page.getByRole('button').filter({ hasText: 'Remove' })).toBeDisabled();
await expect(page.getByRole('button').filter({ hasText: 'Add' })).toBeDisabled();
});

test('extract exercise split from mesocycle', async ({ page }) => {
await createMesocycle(page, { exerciseSplitCreated: true });
await page.getByRole('link', { name: 'MyMeso' }).first().click();
await expect(page.getByRole('main')).toContainText(new Date().toLocaleDateString());
await page.getByRole('tab', { name: 'Split' }).click();
await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Next' }).click();
Expand Down

0 comments on commit 50c79c6

Please sign in to comment.