@kalxjs/core
Version:
A modern JavaScript framework for building user interfaces with reactive state, composition API, and built-in performance optimizations
408 lines (342 loc) • 10.2 kB
JavaScript
/**
* Mock Utilities for Testing
* Mock router, store, and other dependencies
*
* @module @kalxjs/testing/mocks
*/
/**
* Create mock function
*/
export function createMockFn(implementation) {
const calls = [];
const returns = [];
function mockFn(...args) {
calls.push(args);
if (implementation) {
const result = implementation(...args);
returns.push(result);
return result;
}
const returnValue = mockFn._returnValue;
returns.push(returnValue);
return returnValue;
}
mockFn.mock = {
calls,
returns,
get callCount() {
return calls.length;
},
};
mockFn._returnValue = undefined;
mockFn.mockReturnValue = (value) => {
mockFn._returnValue = value;
return mockFn;
};
mockFn.mockReturnValueOnce = (value) => {
const currentImpl = implementation;
let called = false;
implementation = (...args) => {
if (!called) {
called = true;
return value;
}
return currentImpl ? currentImpl(...args) : mockFn._returnValue;
};
return mockFn;
};
mockFn.mockResolvedValue = (value) => {
mockFn._returnValue = Promise.resolve(value);
return mockFn;
};
mockFn.mockRejectedValue = (error) => {
mockFn._returnValue = Promise.reject(error);
return mockFn;
};
mockFn.mockImplementation = (fn) => {
implementation = fn;
return mockFn;
};
mockFn.mockClear = () => {
calls.length = 0;
returns.length = 0;
return mockFn;
};
mockFn.mockReset = () => {
mockFn.mockClear();
implementation = null;
mockFn._returnValue = undefined;
return mockFn;
};
return mockFn;
}
/**
* Create spy function
*/
export function createSpy(object, method) {
const original = object[method];
const mockFn = createMockFn(original.bind(object));
object[method] = mockFn;
mockFn.mockRestore = () => {
object[method] = original;
};
return mockFn;
}
/**
* Mock Router
*/
export function createMockRouter(options = {}) {
const {
currentRoute = {
path: '/',
query: {},
params: {},
name: null,
meta: {},
},
routes = [],
} = options;
const router = {
currentRoute: { ...currentRoute },
routes,
history: [currentRoute.path],
push: createMockFn((to) => {
const path = typeof to === 'string' ? to : to.path;
router.currentRoute = {
path,
query: to.query || {},
params: to.params || {},
name: to.name || null,
meta: to.meta || {},
};
router.history.push(path);
return Promise.resolve();
}),
replace: createMockFn((to) => {
const path = typeof to === 'string' ? to : to.path;
router.currentRoute = {
path,
query: to.query || {},
params: to.params || {},
name: to.name || null,
meta: to.meta || {},
};
router.history[router.history.length - 1] = path;
return Promise.resolve();
}),
go: createMockFn((delta) => {
// Simplified implementation
const newIndex = Math.max(0, router.history.length - 1 + delta);
router.currentRoute.path = router.history[newIndex];
}),
back: createMockFn(() => {
router.go(-1);
}),
forward: createMockFn(() => {
router.go(1);
}),
beforeEach: createMockFn(),
afterEach: createMockFn(),
onError: createMockFn(),
};
return router;
}
/**
* Mock Store
*/
export function createMockStore(options = {}) {
const {
state = {},
getters = {},
mutations = {},
actions = {},
modules = {},
} = options;
const store = {
state: { ...state },
getters: {},
_mutations: { ...mutations },
_actions: { ...actions },
_modules: { ...modules },
_subscribers: [],
commit: createMockFn((type, payload) => {
if (store._mutations[type]) {
store._mutations[type](store.state, payload);
store._subscribers.forEach(sub => sub({ type, payload }, store.state));
}
}),
dispatch: createMockFn((type, payload) => {
if (store._actions[type]) {
return Promise.resolve(store._actions[type]({
state: store.state,
commit: store.commit,
dispatch: store.dispatch,
getters: store.getters,
}, payload));
}
return Promise.resolve();
}),
subscribe: createMockFn((fn) => {
store._subscribers.push(fn);
return () => {
const index = store._subscribers.indexOf(fn);
if (index > -1) {
store._subscribers.splice(index, 1);
}
};
}),
replaceState: createMockFn((newState) => {
store.state = { ...newState };
}),
};
// Setup getters
Object.keys(getters).forEach(key => {
Object.defineProperty(store.getters, key, {
get: () => getters[key](store.state, store.getters),
enumerable: true,
});
});
return store;
}
/**
* Mock API calls
*/
export function createMockAPI() {
const responses = new Map();
const calls = [];
const api = {
get: createMockFn((url, config) => {
calls.push({ method: 'GET', url, config });
return Promise.resolve(responses.get(`GET:${url}`) || { data: {} });
}),
post: createMockFn((url, data, config) => {
calls.push({ method: 'POST', url, data, config });
return Promise.resolve(responses.get(`POST:${url}`) || { data: {} });
}),
put: createMockFn((url, data, config) => {
calls.push({ method: 'PUT', url, data, config });
return Promise.resolve(responses.get(`PUT:${url}`) || { data: {} });
}),
delete: createMockFn((url, config) => {
calls.push({ method: 'DELETE', url, config });
return Promise.resolve(responses.get(`DELETE:${url}`) || { data: {} });
}),
mockResponse: (method, url, response) => {
responses.set(`${method}:${url}`, response);
},
getCalls: () => [...calls],
clearCalls: () => {
calls.length = 0;
},
};
return api;
}
/**
* Mock LocalStorage
*/
export function createMockStorage() {
const storage = new Map();
return {
getItem: createMockFn((key) => {
return storage.get(key) || null;
}),
setItem: createMockFn((key, value) => {
storage.set(key, String(value));
}),
removeItem: createMockFn((key) => {
storage.delete(key);
}),
clear: createMockFn(() => {
storage.clear();
}),
get length() {
return storage.size;
},
key: createMockFn((index) => {
return Array.from(storage.keys())[index] || null;
}),
};
}
/**
* Mock timers
*/
export function useFakeTimers() {
const timers = [];
let now = Date.now();
const originalSetTimeout = global.setTimeout;
const originalSetInterval = global.setInterval;
const originalClearTimeout = global.clearTimeout;
const originalClearInterval = global.clearInterval;
const originalDateNow = Date.now;
global.setTimeout = (fn, delay, ...args) => {
const id = timers.length;
timers.push({
id,
type: 'timeout',
fn,
delay,
args,
time: now + delay,
});
return id;
};
global.setInterval = (fn, delay, ...args) => {
const id = timers.length;
timers.push({
id,
type: 'interval',
fn,
delay,
args,
time: now + delay,
});
return id;
};
global.clearTimeout = (id) => {
const index = timers.findIndex(t => t.id === id);
if (index > -1) {
timers.splice(index, 1);
}
};
global.clearInterval = global.clearTimeout;
Date.now = () => now;
return {
tick: (ms) => {
now += ms;
const toRun = timers.filter(t => t.time <= now);
toRun.forEach(timer => {
timer.fn(...timer.args);
if (timer.type === 'interval') {
timer.time = now + timer.delay;
} else {
const index = timers.indexOf(timer);
if (index > -1) {
timers.splice(index, 1);
}
}
});
},
runAll: () => {
while (timers.length > 0) {
const next = timers.reduce((a, b) => a.time < b.time ? a : b);
now = next.time;
next.fn(...next.args);
if (next.type === 'timeout') {
const index = timers.indexOf(next);
if (index > -1) {
timers.splice(index, 1);
}
} else {
next.time = now + next.delay;
}
}
},
restore: () => {
global.setTimeout = originalSetTimeout;
global.setInterval = originalSetInterval;
global.clearTimeout = originalClearTimeout;
global.clearInterval = originalClearInterval;
Date.now = originalDateNow;
},
};
}