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
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ describe('injectIsFetching', () => {
expect(rendered.getByText('fetching: 0')).toBeInTheDocument()
})

it('should be able to filter by queryKey', async () => {
const key1 = queryKey()
const key2 = queryKey()

@Component({
template: `<div>fetching: {{ isFetching() }}</div>`,
})
class Page {
readonly query1 = injectQuery(() => ({
queryKey: key1,
queryFn: () => sleep(10).then(() => 'test1'),
}))
readonly query2 = injectQuery(() => ({
queryKey: key2,
queryFn: () => sleep(100).then(() => 'test2'),
}))
readonly isFetching = injectIsFetching({ queryKey: key1 })
}

const rendered = await render(Page)

await vi.advanceTimersByTimeAsync(0)
rendered.fixture.detectChanges()
expect(rendered.getByText('fetching: 1')).toBeInTheDocument()

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()
expect(rendered.getByText('fetching: 0')).toBeInTheDocument()
})

describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
expect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,39 @@ describe('injectIsMutating', () => {
expect(rendered.getByText('mutating: 0')).toBeInTheDocument()
})

it('should be able to filter by mutationKey', async () => {
const key1 = queryKey()
const key2 = queryKey()

@Component({
template: `<div>mutating: {{ isMutating() }}</div>`,
})
class Page {
readonly mutation1 = injectMutation(() => ({
mutationKey: key1,
mutationFn: () => sleep(10).then(() => 'data1'),
}))
readonly mutation2 = injectMutation(() => ({
mutationKey: key2,
mutationFn: () => sleep(100).then(() => 'data2'),
}))
readonly isMutating = injectIsMutating({ mutationKey: key1 })
}

const rendered = await render(Page)

rendered.fixture.componentInstance.mutation1.mutate()
rendered.fixture.componentInstance.mutation2.mutate()

await vi.advanceTimersByTimeAsync(0)
rendered.fixture.detectChanges()
expect(rendered.getByText('mutating: 1')).toBeInTheDocument()

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()
expect(rendered.getByText('mutating: 0')).toBeInTheDocument()
})

describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
expect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ describe('injectIsRestoring', () => {
expect(isRestoring()).toBe(true)
})

it('should reactively reflect changes to the provided signal', () => {
const restoringSignal = signal(true)

TestBed.configureTestingModule({
providers: [provideIsRestoring(restoringSignal.asReadonly())],
})

const isRestoring = TestBed.runInInjectionContext(() => {
return injectIsRestoring()
})

expect(isRestoring()).toBe(true)

restoringSignal.set(false)
expect(isRestoring()).toBe(false)

restoringSignal.set(true)
expect(isRestoring()).toBe(true)
})

it('should be usable outside injection context when passing an injector', () => {
const isRestoring = injectIsRestoring({
injector: TestBed.inject(Injector),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,47 +50,67 @@ describe('injectMutation', () => {
it('should change state after invoking mutate', async () => {
const result = 'Mock data'

const mutation = TestBed.runInInjectionContext(() => {
return injectMutation(() => ({
@Component({
template: `
<div>isIdle: {{ mutation.isIdle() }}</div>
<div>isPending: {{ mutation.isPending() }}</div>
<div>isError: {{ mutation.isError() }}</div>
<div>isSuccess: {{ mutation.isSuccess() }}</div>
<div>data: {{ mutation.data() ?? 'none' }}</div>
<div>error: {{ mutation.error()?.message ?? 'none' }}</div>
`,
})
class Page {
readonly mutation = injectMutation(() => ({
mutationFn: (params: string) => sleep(10).then(() => params),
}))
})
}

TestBed.tick()
const rendered = await render(Page)

mutation.mutate(result)
rendered.fixture.componentInstance.mutation.mutate(result)
await vi.advanceTimersByTimeAsync(0)
rendered.fixture.detectChanges()

expectSignals(mutation, {
isIdle: false,
isPending: true,
isError: false,
isSuccess: false,
data: undefined,
error: null,
})
expect(rendered.getByText('isIdle: false')).toBeInTheDocument()
expect(rendered.getByText('isPending: true')).toBeInTheDocument()
expect(rendered.getByText('isError: false')).toBeInTheDocument()
expect(rendered.getByText('isSuccess: false')).toBeInTheDocument()
expect(rendered.getByText('data: none')).toBeInTheDocument()
expect(rendered.getByText('error: none')).toBeInTheDocument()
})

it('should return error when request fails', async () => {
const mutation = TestBed.runInInjectionContext(() => {
return injectMutation(() => ({
@Component({
template: `
<div>isIdle: {{ mutation.isIdle() }}</div>
<div>isPending: {{ mutation.isPending() }}</div>
<div>isError: {{ mutation.isError() }}</div>
<div>isSuccess: {{ mutation.isSuccess() }}</div>
<div>data: {{ mutation.data() ?? 'none' }}</div>
<div>error: {{ mutation.error()?.message ?? 'none' }}</div>
`,
})
class Page {
readonly mutation = injectMutation(() => ({
mutationFn: () =>
sleep(10).then(() => Promise.reject(new Error('Some error'))),
}))
})
}

mutation.mutate()
const rendered = await render(Page)

rendered.fixture.componentInstance.mutation.mutate()

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()

expectSignals(mutation, {
isIdle: false,
isPending: false,
isError: true,
isSuccess: false,
data: undefined,
error: Error('Some error'),
})
expect(rendered.getByText('isIdle: false')).toBeInTheDocument()
expect(rendered.getByText('isPending: false')).toBeInTheDocument()
expect(rendered.getByText('isError: true')).toBeInTheDocument()
expect(rendered.getByText('isSuccess: false')).toBeInTheDocument()
expect(rendered.getByText('data: none')).toBeInTheDocument()
expect(rendered.getByText('error: Some error')).toBeInTheDocument()
})

it('should return data when request succeeds', async () => {
Expand Down Expand Up @@ -498,6 +518,21 @@ describe('injectMutation', () => {
await expect(() => mutateAsync()).rejects.toThrow(err)
})

it('should resolve mutateAsync with the value returned from mutationFn', async () => {
const key = queryKey()
const { mutateAsync } = TestBed.runInInjectionContext(() => {
return injectMutation(() => ({
mutationKey: key,
mutationFn: (params: string) => sleep(10).then(() => params),
}))
})

const promise = mutateAsync('Mock data')
await vi.advanceTimersByTimeAsync(11)

await expect(promise).resolves.toBe('Mock data')
})

describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
const key = queryKey()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,50 @@ describe('injectQueries', () => {
expect(results.length).toBeGreaterThanOrEqual(2)
})

it('should reflect error state when one of the queries rejects', async () => {
const key1 = queryKey()
const key2 = queryKey()

@Component({
template: `
<div>
status1: {{ result()[0].status() }}, error1:
{{ result()[0].error()?.message ?? 'none' }}
</div>
<div>
status2: {{ result()[1].status() }}, data2:
{{ result()[1].data() ?? 'none' }}
</div>
`,
})
class Page {
readonly result = injectQueries(() => ({
queries: [
{
queryKey: key1,
queryFn: () =>
sleep(10).then(() => Promise.reject(new Error('Some error'))),
retry: false,
},
{
queryKey: key2,
queryFn: () => sleep(10).then(() => 2),
},
],
}))
}

const rendered = await render(Page)

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()

expect(
rendered.getByText('status1: error, error1: Some error'),
).toBeInTheDocument()
expect(rendered.getByText('status2: success, data2: 2')).toBeInTheDocument()
})

describe('isRestoring', () => {
it('should not fetch for the duration of the restoring period when isRestoring is true', async () => {
const key1 = queryKey()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,62 @@ describe('injectQuery', () => {
expect(rendered.getByText('failureReason: Some error')).toBeInTheDocument()
})

it('should be able to select a part of the data with select', async () => {
const key = queryKey()

@Component({
template: `<div>data: {{ query.data() ?? 'none' }}</div>`,
})
class Page {
readonly query = injectQuery<{ name: string }, Error, string>(() => ({
queryKey: key,
queryFn: () => sleep(10).then(() => ({ name: 'test' })),
select: (data) => data.name,
}))
}

const rendered = await render(Page)

expect(rendered.getByText('data: none')).toBeInTheDocument()

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()

expect(rendered.getByText('data: test')).toBeInTheDocument()
})

it('should show placeholderData until queryFn resolves and then expose real data', async () => {
const key = queryKey()

@Component({
template: `
<div>data: {{ query.data() }}</div>
<div>isPlaceholderData: {{ query.isPlaceholderData() }}</div>
<div>isSuccess: {{ query.isSuccess() }}</div>
`,
})
class Page {
readonly query = injectQuery(() => ({
queryKey: key,
queryFn: () => sleep(10).then(() => 'real-data'),
placeholderData: 'placeholder',
}))
}

const rendered = await render(Page)

expect(rendered.getByText('data: placeholder')).toBeInTheDocument()
expect(rendered.getByText('isPlaceholderData: true')).toBeInTheDocument()
expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()

await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()

expect(rendered.getByText('data: real-data')).toBeInTheDocument()
expect(rendered.getByText('isPlaceholderData: false')).toBeInTheDocument()
expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
})

it('should update query on options contained signal change', async () => {
const key1 = queryKey()
const key2 = queryKey()
Expand Down Expand Up @@ -548,24 +604,6 @@ describe('injectQuery', () => {
})
})

it('should set state to error when queryFn returns reject promise', async () => {
const key = queryKey()
const query = TestBed.runInInjectionContext(() => {
return injectQuery(() => ({
retry: false,
queryKey: key,
queryFn: () =>
sleep(10).then(() => Promise.reject(new Error('Some error'))),
}))
})

expect(query.status()).toBe('pending')

await vi.advanceTimersByTimeAsync(11)

expect(query.status()).toBe('error')
})

it('should render with required signal inputs', async () => {
@Component({
selector: 'app-fake',
Expand Down
Loading