diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9bcdb46 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] +} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..9ed9cf2 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,16 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + version: '18.19.0, 18, 20, 22, 23' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 0000000..bac3fac --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,23 @@ +name: Publish Any Commit +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run prepublishOnly --if-present + + - run: npx pkg-pr-new publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1c6cbb1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index ba2a97b..3682328 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ node_modules +.tshy* +.eslintcache +dist coverage +package-lock.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 98813fa..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: node_js -node_js: - - 6 - - 8 - - 10 -notifications: - email: - on_success: change - on_failure: change -sudo: false -after_success: - - npm install -g codecov - - codecov diff --git a/History.md b/CHANGELOG.md similarity index 100% rename from History.md rename to CHANGELOG.md diff --git a/LICENSE b/LICENSE index ddb6522..61351f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (C) 2012 by fent +Copyright (C) 2024 - present node-modules Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9dd455a..bdd430d 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,27 @@ -# muk-prop.js +# @cnpmjs/muk-prop -[![Build Status](https://secure.travis-ci.org/fent/muk-prop.js.svg)](http://travis-ci.org/fent/muk-prop.js) -[![Dependency Status](https://david-dm.org/fent/muk-prop.js.svg)](https://david-dm.org/fent/muk-prop.js) -[![codecov](https://codecov.io/gh/fent/muk-prop.js/branch/master/graph/badge.svg)](https://codecov.io/gh/fent/muk-prop.js) +[![NPM version][npm-image]][npm-url] +[![Node.js CI](https://github.com/node-modules/muk-prop.js/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/muk-prop.js/actions/workflows/nodejs.yml) +[![Test coverage][codecov-image]][codecov-url] +[![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/@cnpmjs/muk-prop.svg?style=flat)](https://nodejs.org/en/download/) + +[npm-image]: https://img.shields.io/npm/v/@cnpmjs/muk-prop.svg?style=flat-square +[npm-url]: https://npmjs.org/package/@cnpmjs/muk-prop +[codecov-image]: https://codecov.io/github/node-modules/muk-prop.js/coverage.svg?branch=master +[codecov-url]: https://codecov.io/github/node-modules/muk-prop.js?branch=master +[download-image]: https://img.shields.io/npm/dm/@cnpmjs/muk-prop.svg?style=flat-square +[download-url]: https://npmjs.org/package/@cnpmjs/muk-prop ![muk](muk.gif) -# Usage +## Usage Object method mocking. ```js const fs = require('fs'); -const muk = require('muk-prop'); +const { muk } = require('@cnpmjs/muk-prop'); muk(fs, 'readFile', (path, callback) => { process.nextTick(callback.bind(null, null, 'file contents here')); @@ -22,7 +31,7 @@ muk(fs, 'readFile', (path, callback) => { Object props mocking with setter/getter. ```js -const muk = require('muk-prop'); +const { muk } = require('@cnpmjs/muk-prop'); const obj = { _a: 1 }; muk(obj, 'a', { @@ -50,15 +59,22 @@ fs.readFile(file, (err, data) => { }); ``` +## Install -# Install - - npm install muk-prop +```bash +npm install @cnpmjs/muk-prop +``` +## Tests -# Tests Tests are written with [mocha](https://mochajs.org) ```bash npm test ``` + +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=node-modules/muk-prop.js)](https://github.com/node-modules/muk-prop.js/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/package.json b/package.json index 5afe5ec..55cd49c 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,68 @@ { "name": "@cnpmjs/muk-prop", + "version": "1.0.0", + "publishConfig": { + "access": "public" + }, "description": "Mock object methods and properties.", "keywords": [ "test", "mock", "stub" ], - "version": "1.0.0", "repository": { "type": "git", "url": "git://github.com/node-modules/muk-prop.js.git" }, - "author": "fent (https://github.com/fent)", - "main": "./lib/index.js", - "scripts": { - "test": "istanbul cover node_modules/.bin/_mocha -- -R spec test/*-test.js" - }, - "directories": { - "lib": "./lib" + "license": "MIT", + "engines": { + "node": ">= 18.19.0" }, "devDependencies": { - "istanbul": "^0.4.5", - "mocha": "^6.0.0" + "@arethetypeswrong/cli": "^0.17.1", + "@eggjs/tsconfig": "1", + "@types/node": "22", + "@types/mocha": "10", + "egg-bin": "6", + "eslint": "8", + "eslint-config-egg": "14", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, - "engines": { - "node": ">=6" + "scripts": { + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run lint -- --fix && npm run prepublishOnly", + "test": "egg-bin test", + "preci": "npm run lint && npm run prepublishOnly && attw --pack", + "ci": "egg-bin cov", + "prepublishOnly": "tshy && tshy-after" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" }, - "license": "MIT", "files": [ - "lib" - ] + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/lib/index.js b/src/index.ts similarity index 67% rename from lib/index.js rename to src/index.ts index b92bd09..54bbb9f 100644 --- a/lib/index.js +++ b/src/index.ts @@ -1,23 +1,24 @@ -'use strict'; - // Keep track of mocks -let mocks = []; -const cache = new Map(); +export interface MockItem { + obj: any; + key: string; + descriptor: PropertyDescriptor; + hasOwnProperty: boolean; +} + +let mocks: MockItem[] = []; +const cache = new Map>(); /** * Mocks a value of an object. - * - * @param {Object} obj - * @param {string} key - * @param {!Function|Object} value */ -const method = module.exports = (obj, key, value) => { - const hasOwnProperty = Object.prototype.hasOwnProperty.call(obj, key); +export function mock(obj: any, key: string, value?: any) { + const hasOwnProperty = Object.hasOwn(obj, key); mocks.push({ obj, key, - descriptor: Object.getOwnPropertyDescriptor(obj, key), + descriptor: Object.getOwnPropertyDescriptor(obj, key)!, // Make sure the key exists on object not the prototype hasOwnProperty, }); @@ -35,7 +36,7 @@ const method = module.exports = (obj, key, value) => { } flag.add(key); - const descriptor = { + const descriptor: PropertyDescriptor = { configurable: true, enumerable: true, }; @@ -51,14 +52,17 @@ const method = module.exports = (obj, key, value) => { } Object.defineProperty(obj, key, descriptor); -}; +} + +// alias to mock +export const muk = mock; /** * Restore all mocks */ -method.restore = () => { +export function restore() { for (let i = mocks.length - 1; i >= 0; i--) { - let m = mocks[i]; + const m = mocks[i]; if (!m.hasOwnProperty) { // Delete the mock key, use key on the prototype delete m.obj[m.key]; @@ -69,9 +73,9 @@ method.restore = () => { } mocks = []; cache.clear(); -}; +} -method.isMocked = (obj, key) => { - let flag = cache.get(obj); +export function isMocked(obj: any, key: string) { + const flag = cache.get(obj); return flag ? flag.has(key) : false; -}; +} diff --git a/test/method-test.js b/test/method.test.ts similarity index 73% rename from test/method-test.js rename to test/method.test.ts index a3803b1..fe63af5 100644 --- a/test/method-test.js +++ b/test/method.test.ts @@ -1,15 +1,12 @@ -'use strict'; - -const muk = require('..'); -const assert = require('assert'); -const fs = require('fs'); - +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import { muk, restore, isMocked, mock } from '../src/index.js'; describe('Mock methods', () => { const readFile = fs.readFile; const mkdir = fs.mkdir; - afterEach(muk.restore); + afterEach(restore); it('Contains original methods', () => { assert.equal(typeof fs.readFile, 'function', @@ -19,11 +16,11 @@ describe('Mock methods', () => { }); it('Methods are new objects after mocked', () => { - const readFileMock = (path, callback) => { + const readFileMock = (_path: string, callback: any) => { process.nextTick(callback.bind(null, null, 'hello!')); }; - const mkdirMock = (path, callback) => { + const mkdirMock = (_path: string, callback: any) => { process.nextTick(callback.bind(null, null)); }; @@ -33,8 +30,8 @@ describe('Mock methods', () => { assert.equal(fs.mkdir, mkdirMock, 'object method is equal to mock'); }); - it('No errors calling new mocked methods', (done) => { - const readFileMock = (path, callback) => { + it('No errors calling new mocked methods', done => { + const readFileMock = (_path: string, callback: any) => { process.nextTick(callback.bind(null, null, 'hello!')); }; muk(fs, 'readFile', readFileMock); @@ -47,29 +44,29 @@ describe('Mock methods', () => { }); it('Should have original methods after muk.restore()', () => { - muk.restore(); + restore(); assert.equal(fs.readFile, readFile, 'original method is restored'); assert.equal(fs.mkdir, mkdir, 'original method is restored'); - const readFileMock = (path, callback) => { + const readFileMock = (_path: string, callback: any) => { process.nextTick(callback.bind(null, null, 'hello!')); }; muk(fs, 'readFile', readFileMock); muk(fs, 'readFile', readFileMock); - muk.restore(); + restore(); assert.equal(fs.readFile, readFile, 'mock twices, original method should be restored too'); }); it('Should mock method on prototype', () => { const readFile = fs.readFile; const newFs = Object.create(fs); - const readFileMock = (path, callback) => { + const readFileMock = (_path: string, callback: any) => { process.nextTick(callback.bind(null, null, 'hello!')); }; muk(newFs, 'readFile', readFileMock); assert.equal(newFs.readFile, readFileMock, 'object method is equal to mock'); - muk.restore(); + restore(); assert.equal(newFs.readFile, readFile, 'object method is equal to origin'); }); }); @@ -77,7 +74,7 @@ describe('Mock methods', () => { describe('Mock property', () => { const config = { enableCache: true, - delay: 10 + delay: 10, }; const plainObj = Object.create(null); @@ -85,13 +82,18 @@ describe('Mock property', () => { const home = process.env.HOME; - afterEach(muk.restore); + afterEach(restore); it('Should mock plain object successfully', () => { muk(plainObj, 'testKey', 'mockValue'); assert.equal(plainObj.testKey, 'mockValue', 'testKey is mockValue'); }); + it('Should alias mock method work', () => { + mock(plainObj, 'testKey', 'mockValue'); + assert.equal(plainObj.testKey, 'mockValue', 'testKey is mockValue'); + }); + it('Contains original property', () => { assert.equal(config.enableCache, true, 'enableCache is true'); assert.equal(config.delay, 10, 'delay is 10'); @@ -114,8 +116,10 @@ describe('Mock property', () => { muk(process.env, 'HOME', '/mockhome'); muk(config, 'notExistProp', 'value'); muk(process.env, 'notExistProp', 0); - muk.restore(); + assert.deepEqual(Object.keys(config), [ 'enableCache', 'delay', 'notExistProp' ]); + restore(); + assert.deepEqual(Object.keys(config), [ 'enableCache', 'delay' ]); assert.equal(config.enableCache, true, 'enableCache is true'); assert.equal(config.delay, 10, 'delay is 10'); assert.equal(process.env.HOME, home, 'process.env.HOME is ' + home); @@ -126,16 +130,21 @@ describe('Mock property', () => { it('Should be undefined when value is not set', () => { muk(config, 'enableCache'); assert.equal(config.enableCache, undefined, 'enableCache is undefined'); + muk(config, 'enableCache', null); + assert.equal(config.enableCache, null, 'enableCache is null'); + muk(config, 'enableCache', undefined); + assert.equal(config.enableCache, undefined, 'enableCache is undefined'); }); it('Should mock property on prototype', () => { const newConfig = Object.create(config); + newConfig.enableCache = true; muk(newConfig, 'enableCache', false); - assert.deepEqual(Object.keys(newConfig), ['enableCache'], 'obj should contain properties'); + assert.deepEqual(Object.keys(newConfig), [ 'enableCache' ], 'obj should contain properties'); assert.equal(newConfig.enableCache, false, 'enableCache is false'); - muk.restore(); - assert.equal(newConfig.enableCache, true, 'enableCache is false'); + restore(); + assert.equal(newConfig.enableCache, true, 'enableCache is true'); }); }); @@ -143,10 +152,10 @@ describe('Mock getter', () => { const obj = { get a() { return 1; - } + }, }; - afterEach(muk.restore); + afterEach(restore); it('Contains original getter', () => { assert.equal(obj.a, 1, 'property a of obj is 1'); @@ -159,17 +168,17 @@ describe('Mock getter', () => { it('Should have original getter after muk.restore()', () => { muk(obj, 'a', 2); - muk.restore(); + restore(); assert.equal(obj.a, 1, 'property a of obj is equal to origin'); }); it('Should mock property on prototype', () => { const newObj = Object.create(obj); muk(newObj, 'a', 2); - assert.deepEqual(Object.keys(newObj), ['a'], 'obj should contain properties'); + assert.deepEqual(Object.keys(newObj), [ 'a' ], 'obj should contain properties'); assert.equal(newObj.a, 2, 'property a of obj is equal to mock'); - muk.restore(); + restore(); assert.equal(newObj.a, 1, 'property a of obj is equal to origin'); }); }); @@ -179,7 +188,7 @@ describe('Mock value with getter', () => { a: 1, }; - afterEach(muk.restore); + afterEach(restore); it('Value are new getter after mocked', () => { muk(obj, 'a', { @@ -191,13 +200,14 @@ describe('Mock value with getter', () => { it('Should throw error when getter', () => { muk(obj, 'a', { get: () => { - throw Error('oh no'); - } + throw new Error('oh no'); + }, }); try { obj.a; } catch (e) { + assert(e instanceof Error); assert.equal(e.message, 'oh no'); } }); @@ -207,7 +217,7 @@ describe('Mock value with getter', () => { get: () => 2, }); - muk.restore(); + restore(); assert.equal(obj.a, 1, 'property a of obj is equal to original'); }); }); @@ -215,19 +225,19 @@ describe('Mock value with getter', () => { describe('Mock value with setter', () => { const obj = { _a: 1, - }; + } as any; Object.defineProperty(obj, 'a', { configurable: true, - set: (value) => obj._a = value, + set: value => { obj._a = value; }, get: () => obj._a, }); - afterEach(muk.restore); + afterEach(restore); it('Value are new setter after mocked', () => { muk(obj, 'a', { - set: (value) => obj._a = value + 1, + set: (value: any) => { obj._a = value + 1; }, get: () => obj._a, }); obj.a = 2; @@ -238,22 +248,22 @@ describe('Mock value with setter', () => { muk(obj, 'a', { set: () => { throw Error('oh no'); - } + }, }); try { obj.a = 2; - } catch (e) { + } catch (e: any) { assert.equal(e.message, 'oh no'); } }); it('Should have original setter after muk.restore()', () => { muk(obj, 'a', { - set: (value) => obj._a = value + 1, + set: (value: number) => { obj._a = value + 1; }, }); - muk.restore(); + restore(); obj.a = 2; assert.equal(obj.a, 2, 'property a of obj is equal to original'); }); @@ -261,16 +271,16 @@ describe('Mock value with setter', () => { describe('Mock check', () => { - afterEach(muk.restore); + afterEach(restore); it('Should check whether is mocked', () => { const obj = { a: 1, }; - assert.equal(muk.isMocked(obj, 'a'), false, 'obj should not be mocked'); + assert.equal(isMocked(obj, 'a'), false, 'obj should not be mocked'); muk(obj, 'a', 2); - assert.ok(muk.isMocked(obj, 'a'), 'obj should be mocked'); + assert.ok(isMocked(obj, 'a'), 'obj should be mocked'); }); it('Should not be enumerable', () => { @@ -278,13 +288,13 @@ describe('Mock check', () => { a: 1, }; muk(obj, 'a', 2); - assert.deepEqual(Object.keys(obj), ['a']); + assert.deepEqual(Object.keys(obj), [ 'a' ]); const keys = []; - for (let key in obj) { + for (const key in obj) { keys.push(key); } - assert.deepEqual(keys, ['a']); + assert.deepEqual(keys, [ 'a' ]); }); it('Should be restored', () => { @@ -292,10 +302,10 @@ describe('Mock check', () => { a: 1, }; muk(obj, 'a', 2); - assert.equal(muk.isMocked(obj, 'a'), true); - muk.restore(); + assert.equal(isMocked(obj, 'a'), true); + restore(); assert.equal(obj.a, 1); - assert.equal(muk.isMocked(obj, 'a'), false); + assert.equal(isMocked(obj, 'a'), false); }); it('Should check different type', () => { @@ -309,24 +319,24 @@ describe('Mock check', () => { }, }; muk(obj, 'a', 2); - assert.ok(muk.isMocked(obj, 'a')); + assert.ok(isMocked(obj, 'a')); muk(obj, 'b', '2'); - assert.ok(muk.isMocked(obj, 'b')); + assert.ok(isMocked(obj, 'b')); muk(obj, 'c', false); - assert.ok(muk.isMocked(obj, 'c')); + assert.ok(isMocked(obj, 'c')); muk(obj, 'd', { d: 1 }); - assert.ok(muk.isMocked(obj, 'd')); + assert.ok(isMocked(obj, 'd')); muk(obj, 'e', 2); - assert.ok(muk.isMocked(obj, 'e')); + assert.ok(isMocked(obj, 'e')); }); it('Should check process.env', () => { muk(process.env, 'HOME', '/mockhome'); assert.equal(process.env.HOME, '/mockhome'); - assert.ok(muk.isMocked(process.env, 'HOME')); + assert.ok(isMocked(process.env, 'HOME')); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +}