-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1632 from SUI-Components/adr-mother-object-msw
[ADR] Mother Objects
- Loading branch information
Showing
10 changed files
with
472 additions
and
9 deletions.
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
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 |
---|---|---|
@@ -1,10 +1,58 @@ | ||
/* global __MOCKS_API_PATH__ */ | ||
import {rest} from 'msw' | ||
|
||
import {getBrowserMocker} from './browser.js' | ||
import {getServerMocker} from './server.js' | ||
|
||
const isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' | ||
|
||
const setupMocker = isNode ? getServerMocker : getBrowserMocker | ||
const generateHandlerFromContext = requester => { | ||
const handlers = requester | ||
.keys() | ||
.filter(path => path.startsWith('./')) | ||
.map(key => { | ||
const module = requester(key) | ||
return { | ||
path: key, | ||
...(module.get && {get: module.get}), | ||
...(module.post && {post: module.post}), | ||
...(module.put && {put: module.put}), | ||
...(module.del && {delete: module.del}), | ||
...(module.patch && {patch: module.patch}) | ||
} | ||
}) | ||
.map(descriptor => { | ||
const {path, ...handlers} = descriptor | ||
const url = path.replace('./', 'https://').replace('/index.js', '') | ||
return Object.entries(handlers).map(([method, handler]) => { | ||
return rest[method](url, async (req, res, ctx) => { | ||
const body = ['POST', 'PATCH'].includes(req.method) ? await req.json() : '' // eslint-disable-line | ||
const [status, json] = await handler({ | ||
headers: req.headers.all(), | ||
params: req.params, | ||
query: Object.fromEntries(req.url.searchParams), | ||
cookies: req.cookies, | ||
body | ||
}) | ||
return res(ctx.status(status), ctx.json(json)) | ||
}) | ||
}) | ||
}) | ||
.flat(Infinity) | ||
return handlers | ||
} | ||
|
||
const setupMocker = legacyHandlers => { | ||
const mocker = isNode ? getServerMocker : getBrowserMocker | ||
let apiContextRequest | ||
try { | ||
apiContextRequest = require.context(__MOCKS_API_PATH__, true, /index\.js$/) | ||
} catch (err) { | ||
console.error(`[sui-mock] Not found route folder in ${__MOCKS_API_PATH__} autoload of msw handlers disabled`) | ||
apiContextRequest = false | ||
} | ||
|
||
return mocker([...legacyHandlers, ...(apiContextRequest && generateHandlerFromContext(apiContextRequest))]) | ||
} | ||
|
||
export {setupMocker, rest} |
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
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,107 @@ | ||
import {faker} from '@faker-js/faker' | ||
|
||
const IDENTITY_FN = i => i | ||
|
||
let instanceRndID | ||
export class RandomID { | ||
static create() { | ||
if (instanceRndID) return instanceRndID | ||
const seed = process.env.SEED || Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) | ||
instanceRndID = new RandomID(seed) | ||
|
||
console.log(`[RandomID.create] Faker created with seed: ${seed}`) | ||
return instanceRndID | ||
} | ||
|
||
static restore() { | ||
instanceRndID = undefined | ||
} | ||
|
||
constructor(id) { | ||
this._id = id | ||
} | ||
|
||
get id() { | ||
return this._id | ||
} | ||
} | ||
|
||
export class FakeGenerator { | ||
static create() { | ||
const seed = RandomID.create().id | ||
|
||
return new FakeGenerator(seed) | ||
} | ||
|
||
constructor(seed) { | ||
faker.seed(seed) | ||
this._faker = faker | ||
} | ||
|
||
city() { | ||
return this._faker.location.city() | ||
} | ||
|
||
email() { | ||
return this._faker.internet.email() | ||
} | ||
|
||
uuid() { | ||
return this._faker.string.uuid() | ||
} | ||
|
||
words({count, replacer = IDENTITY_FN}) { | ||
return replacer(this._faker.lorem.words(count)) | ||
} | ||
|
||
color() { | ||
return this._faker.color.rgb({format: 'hex', casing: 'lower'}) | ||
} | ||
|
||
date({from, to} = {}) { | ||
return this._faker.date.between({from, to}) | ||
} | ||
|
||
pick(list) { | ||
return list[Math.floor(Math.random() * list.length)] | ||
} | ||
|
||
bool() { | ||
return Math.random() < 0.5 | ||
} | ||
|
||
province() { | ||
return this._faker.location.county() | ||
} | ||
|
||
phone() { | ||
return this._faker.phone.number() | ||
} | ||
|
||
URL() { | ||
return this._faker.internet.url() | ||
} | ||
|
||
imgURL({removeHost} = {removeHost: true}) { | ||
const url = this._faker.image.imageUrl() | ||
|
||
if (!removeHost) return url | ||
|
||
const uri = new URL(url) | ||
return uri.pathname.replace('/', '') | ||
} | ||
|
||
number({min = 0, max = Infinity}) { | ||
const difference = max - min | ||
|
||
let rand = Math.random() | ||
rand = Math.floor(rand * difference) | ||
rand = rand + min | ||
|
||
return rand | ||
} | ||
|
||
zipCode() { | ||
return this._faker.location.zipCode() | ||
} | ||
} |
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,76 @@ | ||
let _instance | ||
export class MockFetcherManager { | ||
_fakeRequests | ||
_methods = ['get', 'patch', 'post'] | ||
|
||
static create(FetchFetcher, {InlineError}) { | ||
if (_instance) return _instance | ||
_instance = new MockFetcherManager(FetchFetcher, {InlineError}) | ||
return _instance | ||
} | ||
|
||
static restore() { | ||
if (!_instance) return console.warn('Unable to restore a non-initialized MockFetcherManager') | ||
_instance.restore() | ||
_instance = undefined | ||
} | ||
|
||
constructor(FetchFetcher, {InlineError}) { | ||
this.FetchFetcher = FetchFetcher | ||
this._originalsFetcher = this._methods.map(method => this.FetchFetcher.prototype[method]) | ||
this._fakeRequests = {} | ||
this._hasInlineErrors = InlineError | ||
this._methods.forEach(method => { | ||
this.FetchFetcher.prototype[method] = this._request(method) | ||
}) | ||
} | ||
|
||
addMock({url, method, error, mock, force}) { | ||
const key = method.toUpperCase() + '::' + url | ||
if (!force && this._fakeRequests[key]) | ||
throw new Error(`[MockFetcherManager#addMock] forbidden overwrite the request ${key}`) | ||
|
||
this._fakeRequests[key] = [error, mock] | ||
} | ||
|
||
validate({url, method}) { | ||
const key = method.toUpperCase() + '::' + url | ||
if (this._fakeRequests[key]) throw new Error(`[MockFetcherManager#validate] request ${key} don't consume`) | ||
} | ||
|
||
restore() { | ||
if (Object.keys(this._fakeRequests).length === 0) { | ||
this._methods.forEach((method, index) => (this.FetchFetcher.prototype[method] = this._originalsFetcher[index])) | ||
} else { | ||
throw new Error(`[MockFetcherManager#restore] | ||
Unabled restore the FetchFetcher because there are request w/out been consume | ||
- Dont match any mock: | ||
${Object.keys(this._fakeRequests).join('\n\t\t')} | ||
`) | ||
} | ||
} | ||
|
||
_request(method) { | ||
const self = this | ||
return function (...args) { | ||
const [url] = args | ||
const requestKey = method.toUpperCase() + '::' + url | ||
// this === FetchFetcher instance | ||
if (self._fakeRequests[requestKey]) { | ||
const [error, response] = self._fakeRequests[requestKey] | ||
delete self._fakeRequests[requestKey] | ||
if (!self._hasInlineErrors) return error ? Promise.reject(error) : Promise.resolve({data: response}) | ||
|
||
return Promise.resolve([error, response]) | ||
} else { | ||
console.warn(`[MockFetcherManager#_request] | ||
- Request make ${requestKey} | ||
- Dont match any mock: | ||
${Object.keys(self._fakeRequests).join('\n\t\t')} | ||
`) | ||
} | ||
|
||
return this._originalsFetcher[method].apply(this, args) | ||
} | ||
} | ||
} |
Oops, something went wrong.