-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
871c293
commit 7252a30
Showing
4 changed files
with
274 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import sinon from 'sinon'; | ||
import { setupStore } from '__test__/__mockData__/setupStore'; | ||
import { waitFor } from '@testing-library/react'; | ||
import { BountyCardStore } from '../bountyCard'; | ||
|
||
describe('BountyCardStore', () => { | ||
let store: BountyCardStore; | ||
let fetchStub: sinon.SinonStub; | ||
const mockWorkspaceId = 'test-workspace-123'; | ||
|
||
beforeAll(() => { | ||
setupStore(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fetchStub = sinon.stub(global, 'fetch'); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('constructor', () => { | ||
it('should initialize with correct default values', async () => { | ||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
|
||
expect(store.bountyCards).toEqual([]); | ||
expect(store.currentWorkspaceId).toBe(mockWorkspaceId); | ||
expect(store.loading).toBeFalsy(); | ||
expect(store.pagination).toEqual({ | ||
currentPage: 1, | ||
pageSize: 10, | ||
total: 0 | ||
}); | ||
}); | ||
}); | ||
|
||
describe('loadWorkspaceBounties', () => { | ||
it('should handle successful bounty cards fetch', async () => { | ||
const mockBounties = [{ id: 1, title: 'Test Bounty' }]; | ||
fetchStub.resolves({ | ||
ok: true, | ||
json: async () => mockBounties | ||
} as Response); | ||
|
||
store = await waitFor(() => new BountyCardStore(mockWorkspaceId)); | ||
|
||
expect(store.bountyCards).toEqual(mockBounties); | ||
expect(store.loading).toBe(false); | ||
expect(store.error).toBeNull(); | ||
}); | ||
|
||
it('should handle failed bounty cards fetch', async () => { | ||
const errorMessage = 'Failed to load bounties'; | ||
fetchStub.resolves({ | ||
ok: false, | ||
statusText: errorMessage | ||
} as Response); | ||
|
||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
expect(store.bountyCards).toEqual([]); | ||
expect(store.loading).toBe(false); | ||
expect(store.error).toBe(`Failed to load bounties: ${errorMessage}`); | ||
}); | ||
}); | ||
|
||
describe('switchWorkspace', () => { | ||
it('should switch workspace and reload bounties', async () => { | ||
const newWorkspaceId = 'new-workspace-456'; | ||
const mockBounties = [{ id: 2, title: 'New Bounty' }]; | ||
|
||
fetchStub.resolves({ | ||
ok: true, | ||
json: async () => mockBounties | ||
} as Response); | ||
|
||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
|
||
await waitFor(() => store.switchWorkspace(newWorkspaceId)); | ||
expect(store.currentWorkspaceId).toBe(newWorkspaceId); | ||
expect(store.pagination.currentPage).toBe(1); | ||
expect(store.bountyCards).toEqual(mockBounties); | ||
}); | ||
|
||
it('should not reload if workspace id is the same', async () => { | ||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
const initialFetchCount = fetchStub.callCount; | ||
|
||
await waitFor(() => store.switchWorkspace(mockWorkspaceId)); | ||
|
||
expect(fetchStub.callCount).toBe(initialFetchCount); | ||
}); | ||
}); | ||
|
||
describe('loadNextPage', () => { | ||
it('should not load next page if already loading', async () => { | ||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
|
||
store.loading = true; | ||
|
||
await waitFor(() => store.loadNextPage()); | ||
}); | ||
|
||
it('should not load next page if all items are loaded', async () => { | ||
store = await waitFor(() => (store = new BountyCardStore(mockWorkspaceId))); | ||
|
||
store.pagination.total = 10; | ||
store.pagination.currentPage = 1; | ||
store.pagination.pageSize = 10; | ||
|
||
await waitFor(() => store.loadNextPage()); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { makeAutoObservable, runInAction } from 'mobx'; | ||
import { TribesURL } from 'config'; | ||
import { useMemo } from 'react'; | ||
import { BountyCard } from './interface'; | ||
import { uiStore } from './ui'; | ||
|
||
export class BountyCardStore { | ||
bountyCards: BountyCard[] = []; | ||
currentWorkspaceId: string; | ||
loading = false; | ||
error: string | null = null; | ||
pagination = { | ||
currentPage: 1, | ||
pageSize: 10, | ||
total: 0 | ||
}; | ||
|
||
constructor(workspaceId: string) { | ||
this.currentWorkspaceId = workspaceId; | ||
makeAutoObservable(this); | ||
this.loadWorkspaceBounties(); | ||
} | ||
|
||
private constructQueryParams(): string { | ||
const { currentPage, pageSize } = this.pagination; | ||
return new URLSearchParams({ | ||
page: currentPage.toString(), | ||
limit: pageSize.toString() | ||
}).toString(); | ||
} | ||
|
||
loadWorkspaceBounties = async (): Promise<void> => { | ||
if (!this.currentWorkspaceId || !uiStore.meInfo?.tribe_jwt) { | ||
runInAction(() => { | ||
this.error = 'Missing workspace ID or authentication'; | ||
}); | ||
return; | ||
} | ||
|
||
try { | ||
runInAction(() => { | ||
this.loading = true; | ||
this.error = null; | ||
}); | ||
|
||
const queryParams = this.constructQueryParams(); | ||
const response = await fetch( | ||
`${TribesURL}/gobounties/bounty-cards?workspace_uuid=${this.currentWorkspaceId}&${queryParams}`, | ||
{ | ||
method: 'GET', | ||
headers: { | ||
'x-jwt': uiStore.meInfo.tribe_jwt, | ||
'Content-Type': 'application/json' | ||
} | ||
} | ||
); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Failed to load bounties: ${response.statusText}`); | ||
} | ||
|
||
const data = (await response.json()) as BountyCard[] | null; | ||
|
||
runInAction(() => { | ||
if (this.pagination.currentPage === 1) { | ||
console.log({ workspace: this.currentWorkspaceId, data }); | ||
this.bountyCards = data || []; | ||
} else { | ||
this.bountyCards = [...this.bountyCards, ...(data || [])]; | ||
} | ||
this.pagination.total = data?.length || 0; | ||
}); | ||
} catch (error) { | ||
runInAction(() => { | ||
this.error = error instanceof Error ? error.message : 'An unknown error occurred'; | ||
}); | ||
} finally { | ||
runInAction(() => { | ||
this.loading = false; | ||
}); | ||
} | ||
}; | ||
|
||
switchWorkspace = async (newWorkspaceId: string): Promise<void> => { | ||
if (this.currentWorkspaceId === newWorkspaceId) return; | ||
|
||
runInAction(() => { | ||
this.currentWorkspaceId = newWorkspaceId; | ||
this.pagination.currentPage = 1; | ||
this.bountyCards = []; | ||
}); | ||
|
||
await this.loadWorkspaceBounties(); | ||
}; | ||
|
||
loadNextPage = async (): Promise<void> => { | ||
if ( | ||
this.loading || | ||
this.pagination.currentPage * this.pagination.pageSize >= this.pagination.total | ||
) { | ||
return; | ||
} | ||
|
||
runInAction(() => { | ||
this.pagination.currentPage += 1; | ||
}); | ||
|
||
await this.loadWorkspaceBounties(); | ||
}; | ||
} | ||
|
||
export const useBountyCardStore = (workspaceId: string) => | ||
useMemo(() => new BountyCardStore(workspaceId), [workspaceId]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters