Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/lib/components/TaskTables/TaskTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import TaskTableBodyCell from '$lib/components/TaskTables/TaskTableBodyCell.svelte';

import { activeContestTypeStore } from '$lib/stores/active_contest_type.svelte';
import {
contestTableProviders,
type ContestTableProviders,
Expand All @@ -31,7 +32,7 @@
let { taskResults, isLoggedIn }: Props = $props();

// Prepare contest table provider based on the active contest type.
let activeContestType = $state<ContestTableProviders>('abcLatest20Rounds');
let activeContestType = $derived(activeContestTypeStore.get());

let provider: ContestTableProvider = $derived(
contestTableProviders[activeContestType as ContestTableProviders],
Expand Down Expand Up @@ -112,8 +113,10 @@
<ButtonGroup class="m-4 contents-center">
{#each Object.entries(contestTableProviders) as [type, config]}
<Button
onclick={() => (activeContestType = type as ContestTableProviders)}
class={activeContestType === type ? 'active-button-class' : ''}
onclick={() => activeContestTypeStore.set(type as ContestTableProviders)}
class={activeContestTypeStore.isSame(type as ContestTableProviders)
? 'active-button-class'
: ''}
aria-label={config.getMetadata().ariaLabel}
>
{config.getMetadata().buttonLabel}
Expand Down
63 changes: 63 additions & 0 deletions src/lib/stores/active_contest_type.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { type ContestTableProviders } from '$lib/utils/contest_table_provider';

/**
* Store that manages the active contest type selection.
*
* This class uses Svelte's state management to track which contest type
* is currently active button. It provides methods to get, set, and
* compare the active contest type.
*
* The store uses the ContestTableProviders type which represents
* different contest table configurations or data providers,
* with a default value of 'abcLatest20Rounds'.
*/
export class ActiveContestTypeStore {
value = $state<ContestTableProviders>('abcLatest20Rounds');

/**
* Creates an instance with the specified contest type.
*
* @param defaultContestType - The default contest type to initialize.
* Defaults to 'abcLatest20Rounds'.
*/
constructor(defaultContestType: ContestTableProviders = 'abcLatest20Rounds') {
this.value = defaultContestType;
}

/**
* Gets the current contest table providers.
*
* @returns The current value of contest table providers.
*/
get(): ContestTableProviders {
return this.value;
}

/**
* Sets the current contest type to the specified value.
*
* @param newContestType - The contest type to set as the current value
*/
set(newContestType: ContestTableProviders): void {
this.value = newContestType;
}

/**
* Validates if the current contest type matches the provided contest type.
* @param contestType - The contest type to compare against
* @returns `true` if the current contest type matches the provided contest type, `false` otherwise
*/
isSame(contestType: ContestTableProviders): boolean {
return this.value === contestType;
}

/**
* Resets the active contest type to the default value.
* Sets the internal value to 'abcLatest20Rounds'.
*/
reset(): void {
this.value = 'abcLatest20Rounds';
}
}

export const activeContestTypeStore = new ActiveContestTypeStore();
72 changes: 72 additions & 0 deletions src/test/lib/stores/active_contest_type.svelte.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, test, expect, beforeEach } from 'vitest';

import type { ContestTableProviders } from '$lib/utils/contest_table_provider';
import { ActiveContestTypeStore } from '$lib/stores/active_contest_type.svelte';

describe('ActiveContestTypeStore', () => {
let store: ActiveContestTypeStore;

beforeEach(() => {
store = new ActiveContestTypeStore();
});

test('expects to initialize with default value', () => {
expect(store.get()).toBe('abcLatest20Rounds');
});

test('expects to initialize with provided value', () => {
const customStore = new ActiveContestTypeStore('abc319Onwards' as ContestTableProviders);
expect(customStore.get()).toBe('abc319Onwards');
});

test('expects to return the current value when calling get()', () => {
expect(store.get()).toBe('abcLatest20Rounds');

// Change the value and verify get() returns the new value
store.set('abc319Onwards' as ContestTableProviders);
expect(store.get()).toBe('abc319Onwards');
});

test('expects to update the value when calling set()', () => {
store.set('fromAbc212ToAbc318' as ContestTableProviders);
expect(store.value).toBe('fromAbc212ToAbc318');
expect(store.get()).toBe('fromAbc212ToAbc318');

store.set('abc319Onwards' as ContestTableProviders);
expect(store.value).toBe('abc319Onwards');
expect(store.get()).toBe('abc319Onwards');
});

test('expects to correctly determine if contest type is the same with isSame()', () => {
expect(store.isSame('abcLatest20Rounds' as ContestTableProviders)).toBe(true);
expect(store.isSame('abc319Onwards' as ContestTableProviders)).toBe(false);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviders)).toBe(false);

store.set('abc319Onwards' as ContestTableProviders);
expect(store.isSame('abc319Onwards' as ContestTableProviders)).toBe(true);
expect(store.isSame('abcLatest20Rounds' as ContestTableProviders)).toBe(false);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviders)).toBe(false);

store.set('fromAbc212ToAbc318' as ContestTableProviders);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviders)).toBe(true);
expect(store.isSame('abcLatest20Rounds' as ContestTableProviders)).toBe(false);
expect(store.isSame('abc319Onwards' as ContestTableProviders)).toBe(false);
});

test('expects to reset the value to default when calling reset()', () => {
// First change the value to something else
store.set('abc319Onwards' as ContestTableProviders);
expect(store.get()).toBe('abc319Onwards');

// Call reset and verify it goes back to default
store.reset();
expect(store.get()).toBe('abcLatest20Rounds');

// Change to a different value and reset again to verify consistency
store.set('fromAbc212ToAbc318' as ContestTableProviders);
expect(store.get()).toBe('fromAbc212ToAbc318');

store.reset();
expect(store.get()).toBe('abcLatest20Rounds');
});
});
Loading