Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
55 changes: 55 additions & 0 deletions src/lib/stores/active_contest_type.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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;
}
}

export const activeContestTypeStore = new ActiveContestTypeStore();
50 changes: 50 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,50 @@
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 return the current value when calling get()', () => {
expect(store.get()).toBe('abcLatest20Rounds');

// Change the value and verify get() returns the new value
store.value = '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);
});
});
Loading