merge group api request

This commit is contained in:
pa
2026-03-09 20:09:05 +09:00
parent feb04b036f
commit c1a35223d4
32 changed files with 71 additions and 44 deletions
+133
View File
@@ -0,0 +1,133 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
_entityCacheInternals,
fetchWithEntityPolicy,
patchAndRefetchActiveQuery,
patchQueryDataWithRecency
} from '../entityCache';
import { queryClient } from '../client';
describe('entity query cache helpers', () => {
beforeEach(() => {
queryClient.clear();
vi.restoreAllMocks();
});
test('reports cache hit for fresh data', async () => {
const queryKey = ['user', 'usr_1'];
let callCount = 0;
const policy = {
staleTime: 20000,
gcTime: 90000,
retry: 1,
refetchOnWindowFocus: false
};
const queryFn = vi.fn(async () => {
callCount++;
return {
json: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' },
params: { userId: 'usr_1' },
ref: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' }
};
});
const first = await fetchWithEntityPolicy({ queryKey, policy, queryFn });
const second = await fetchWithEntityPolicy({ queryKey, policy, queryFn });
expect(first.cache).toBe(false);
expect(second.cache).toBe(true);
expect(callCount).toBe(1);
});
test('always refetches when staleTime is zero (instance strategy)', async () => {
const queryKey = ['instance', 'wrld_1', '12345'];
let callCount = 0;
const policy = {
staleTime: 0,
gcTime: 10000,
retry: 0,
refetchOnWindowFocus: false
};
const queryFn = vi.fn(async () => {
callCount++;
return {
json: {
id: 'wrld_1:12345',
$fetchedAt: new Date().toJSON()
},
params: { worldId: 'wrld_1', instanceId: '12345' },
ref: {
id: 'wrld_1:12345',
$fetchedAt: new Date().toJSON()
}
};
});
await fetchWithEntityPolicy({ queryKey, policy, queryFn });
await fetchWithEntityPolicy({ queryKey, policy, queryFn });
expect(callCount).toBe(2);
});
test('does not overwrite newer data with older payload', () => {
const queryKey = ['world', 'wrld_1'];
patchQueryDataWithRecency({
queryKey,
nextData: {
ref: { id: 'wrld_1', updated_at: '2026-01-01T00:00:00.000Z' }
}
});
patchQueryDataWithRecency({
queryKey,
nextData: {
ref: { id: 'wrld_1', updated_at: '2025-01-01T00:00:00.000Z' }
}
});
const cached = queryClient.getQueryData(queryKey);
expect(cached.ref.updated_at).toBe('2026-01-01T00:00:00.000Z');
});
test('patch and refetch invalidates only active queries for that key', async () => {
const invalidateSpy = vi
.spyOn(queryClient, 'invalidateQueries')
.mockResolvedValue();
const queryKey = ['avatar', 'avtr_1'];
await patchAndRefetchActiveQuery({
queryKey,
nextData: {
ref: { id: 'avtr_1', updated_at: '2026-01-01T00:00:00.000Z' }
}
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey,
exact: true,
refetchType: 'active'
});
});
test('internal recency guard prefers same-or-newer timestamps', () => {
const newer = {
ref: { id: 'usr_1', updated_at: '2026-02-01T00:00:00.000Z' }
};
const older = {
ref: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' }
};
expect(_entityCacheInternals.shouldReplaceCurrent(older, newer)).toBe(
true
);
expect(_entityCacheInternals.shouldReplaceCurrent(newer, older)).toBe(
false
);
});
});
+63
View File
@@ -0,0 +1,63 @@
import { describe, expect, test } from 'vitest';
import { queryKeys } from '../keys';
describe('query key shapes', () => {
test('favorite world keys include owner and tag dimensions', () => {
const a = queryKeys.favoriteWorlds({
n: 100,
offset: 0,
ownerId: 'usr_1',
userId: 'usr_1',
tag: 'worlds1'
});
const b = queryKeys.favoriteWorlds({
n: 100,
offset: 0,
ownerId: 'usr_2',
userId: 'usr_2',
tag: 'worlds1'
});
expect(a).not.toEqual(b);
});
test('world list keys include query option discriminator', () => {
const base = {
userId: 'usr_me',
n: 50,
offset: 0,
sort: 'updated',
order: 'descending',
user: 'me',
releaseStatus: 'all'
};
const defaultKey = queryKeys.worldsByUser(base);
const featuredKey = queryKeys.worldsByUser({
...base,
option: 'featured'
});
expect(defaultKey).not.toEqual(featuredKey);
});
test('group member list keys include sort and role dimensions', () => {
const everyone = queryKeys.groupMembers({
groupId: 'grp_1',
n: 100,
offset: 0,
sort: 'joinedAt:desc',
roleId: ''
});
const roleScoped = queryKeys.groupMembers({
groupId: 'grp_1',
n: 100,
offset: 0,
sort: 'joinedAt:desc',
roleId: 'grol_1'
});
expect(everyone).not.toEqual(roleScoped);
});
});
+137
View File
@@ -0,0 +1,137 @@
import { describe, expect, test } from 'vitest';
import {
entityQueryPolicies,
getEntityQueryPolicy,
toQueryOptions
} from '../policies';
describe('query policy configuration', () => {
test('matches the finalized cache strategy', () => {
expect(entityQueryPolicies.user).toMatchObject({
staleTime: 20000,
gcTime: 90000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.avatar).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.world).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.group).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.groupCollection).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.worldCollection).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.instance).toMatchObject({
staleTime: 0,
gcTime: 10000,
retry: 0,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.friendList).toMatchObject({
staleTime: 20000,
gcTime: 90000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.favoriteCollection).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.galleryCollection).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.inventoryCollection).toMatchObject({
staleTime: 20000,
gcTime: 120000,
retry: 1,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.fileObject).toMatchObject({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
});
test('exposes entity policy lookup', () => {
expect(getEntityQueryPolicy('user')).toBe(entityQueryPolicies.user);
expect(getEntityQueryPolicy('avatar')).toBe(entityQueryPolicies.avatar);
expect(getEntityQueryPolicy('world')).toBe(entityQueryPolicies.world);
expect(getEntityQueryPolicy('group')).toBe(entityQueryPolicies.group);
expect(getEntityQueryPolicy('groupCollection')).toBe(
entityQueryPolicies.groupCollection
);
expect(getEntityQueryPolicy('worldCollection')).toBe(
entityQueryPolicies.worldCollection
);
expect(getEntityQueryPolicy('instance')).toBe(
entityQueryPolicies.instance
);
expect(getEntityQueryPolicy('friendList')).toBe(
entityQueryPolicies.friendList
);
expect(getEntityQueryPolicy('favoriteCollection')).toBe(
entityQueryPolicies.favoriteCollection
);
expect(getEntityQueryPolicy('galleryCollection')).toBe(
entityQueryPolicies.galleryCollection
);
expect(getEntityQueryPolicy('inventoryCollection')).toBe(
entityQueryPolicies.inventoryCollection
);
expect(getEntityQueryPolicy('fileObject')).toBe(
entityQueryPolicies.fileObject
);
});
test('normalizes policy values to query options', () => {
const options = toQueryOptions(entityQueryPolicies.group);
expect(options).toEqual({
staleTime: 60000,
gcTime: 300000,
retry: 1,
refetchOnWindowFocus: false
});
});
});