diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..b007506 --- /dev/null +++ b/.babelrc @@ -0,0 +1,10 @@ +{ + 'presets': [ + 'es2015', + 'stage-0', + 'stage-1', + 'stage-2', + 'stage-3' + ], + 'plugins': ['transform-runtime', 'transform-decorators-legacy'] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..954ff2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Build +dist/ +releases/ +node_modules/ +lib/ +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58c5b3b --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +BASE=$(shell pwd) +SCRIPTS=$(BASE)/scripts + +all: configure pack + +# Install the requirements +configure: + npm install + +# Build the ES5 script +build: + sh "$(SCRIPTS)/build.sh" $(BASE) + +# Builds a ready-to-deploy ZIP file +pack: build + sh "$(SCRIPTS)/release.sh" $(BASE) $(VERSION) + +# Install in Paw extensions folder +install: build + sh "$(SCRIPTS)/transfer.sh" $(BASE) + +# Validation commands +lint: + sh "$(SCRIPTS)/lint.sh" $(BASE) + +test: + sh "$(SCRIPTS)/test.sh" $(BASE) + +check: lint test diff --git a/linting/eslint_base.yaml b/linting/eslint_base.yaml new file mode 100644 index 0000000..6de2a2d --- /dev/null +++ b/linting/eslint_base.yaml @@ -0,0 +1,248 @@ +--- +env: + browser: true + node: true + es6: true +parser: babel-eslint +globals: + ga: true + mixpanel: true + olark: true + Raven: true +ecmaFeatures: + arrowFunctions: true + binaryLiterals: true + blockBindings: true + classes: true + defaultParams: true + destructuring: true + forOf: true + generators: true + modules: true + objectLiteralComputedProperties: true + objectLiteralDuplicateProperties: true + objectLiteralShorthandMethods: true + objectLiteralShorthandProperties: true + octalLiterals: true + regexUFlag: true + regexYFlag: true + spread: true + superInFunctions: true + templateStrings: true + unicodeCodePointEscapes: true + globalReturn: true + jsx: true +rules: + comma-dangle: 2 + no-cond-assign: 2 + no-console: 2 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: 2 + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 2 + valid-typeof: 2 + block-scoped-var: 0 + complexity: 0 + consistent-return: 0 + curly: 2 + default-case: 2 + dot-notation: 2 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 0 + no-caller: 2 + no-div-regex: 2 + no-else-return: 0 # nicer to have strong if/else logic + no-empty-label: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-fallthrough: 2 + no-floating-decimal: 2 + no-implied-eval: 2 + no-iterator: 2 + no-labels: 2 + no-lone-blocks: 2 + no-loop-func: 2 + no-multi-spaces: 2 + no-multi-str: 2 + no-native-reassign: 2 + no-new: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-octal: 2 + no-octal-escape: 2 + no-param-reassign: 2 + no-process-env: 0 + no-proto: 2 + no-redeclare: 2 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 2 + no-throw-literal: 2 + no-unused-expressions: 2 + no-void: 2 + no-warning-comments: + - 0 + - terms: + - todo + - fixme + location: start + no-with: 2 + radix: 2 + vars-on-top: 2 + wrap-iife: 2 + yoda: 2 + strict: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow: 2 + no-shadow-restricted-names: 2 + no-undef: 2 + no-undef-init: 2 + no-undefined: 2 + no-unused-vars: 2 + no-use-before-define: 2 + indent: + - 1 + - 4 + - SwitchCase: 1 + brace-style: + - 1 + - stroustrup + - allowSingleLine: true + camelcase: 0 + comma-spacing: + - 1 + - before: false + after: true + comma-style: + - 1 + - last + consistent-this: + - 1 + - _this + eol-last: 1 + func-names: 0 + func-style: 0 + key-spacing: + - 1 + - beforeColon: false + afterColon: true + max-nested-callbacks: + - 1 + - 3 + new-cap: + - 1 + - newIsCap: true + capIsNew: false + new-parens: 1 + newline-after-var: 0 + no-array-constructor: 1 + no-inline-comments: 1 + no-lonely-if: 1 + no-mixed-spaces-and-tabs: 1 + no-multiple-empty-lines: + - 1 + - max: 2 + no-nested-ternary: 1 + no-new-object: 1 + no-spaced-func: 1 + no-ternary: 0 + no-trailing-spaces: 1 + no-underscore-dangle: 0 + no-extra-parens: 1 + one-var: + - 1 + - never + operator-assignment: 0 + padded-blocks: + - 1 + - never + quote-props: + - 1 + - as-needed + quotes: + - 1 + - single + semi: + - 1 + - "never" + semi-spacing: + - 1 + - before: false + after: true + sort-vars: 0 + space-after-keywords: + - 1 + - always + space-before-blocks: + - 1 + - always + space-before-function-paren: + - 1 + - anonymous: never + named: never + object-curly-spacing: + - 1 + - always + array-bracket-spacing: + - 1 + - always + computed-property-spacing: + - 1 + - never + space-in-parens: + - 1 + - never + space-infix-ops: + - 1 + space-return-throw-case: + - 1 + space-unary-ops: + - 1 + - words: true + nonwords: false + spaced-comment: + - 1 + - always + wrap-regex: 0 + no-var: 2 + generator-star-spacing: + - 2 + - before + max-depth: + - 2 + - 4 + max-len: + - 2 + - 80 + - 2 + max-params: + - 2 + - 6 + max-statements: 0 + no-bitwise: 0 + no-plusplus: 2 + diff --git a/linting/prod.yaml b/linting/prod.yaml new file mode 100644 index 0000000..eaa9fda --- /dev/null +++ b/linting/prod.yaml @@ -0,0 +1,2 @@ +--- +extends: ./eslint_base.yaml diff --git a/package.json b/package.json new file mode 100644 index 0000000..05cf5d9 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "version": "0.1.0", + "dependencies": { + "babel-plugin-transform-flow-strip-types": "6.3.15", + "babel-polyfill": "6.3.14", + "babel-runtime": "6.3.19", + "randexp": "^0.4.3" + }, + "devDependencies": { + "immutable": "3.7.6", + "babel-cli": "6.6.5", + "babel-core": "6.4.0", + "babel-eslint": "4.1.8", + "babel-loader": "6.2.0", + "babel-plugin-rewire": "1.0.0-beta-3", + "babel-plugin-transform-class-properties": "^6.6.0", + "babel-plugin-transform-decorators": "6.3.13", + "babel-plugin-transform-decorators-legacy": "1.3.2", + "babel-plugin-transform-runtime": "6.3.13", + "babel-preset-es2015": "6.3.13", + "babel-preset-stage-0": "6.3.13", + "babel-preset-stage-1": "6.3.13", + "babel-preset-stage-2": "6.3.13", + "babel-preset-stage-3": "6.3.13", + "babel-register": "6.3.13", + "chai": "3.4.1", + "eslint": "1.3.1", + "eslint-loader": "1.0.0", + "json-loader": "^0.5.4", + "mocha": "2.3.4", + "webpack": "1.12.9" + } +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..615f179 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +base=$1 +rm -rf "$base/dist" +node "$base/node_modules/webpack/bin/webpack.js" diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100644 index 0000000..e25edba --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +base=$1 +node "$base/node_modules/eslint/bin/eslint.js" -c "$base/linting/prod.yaml" "$base/src/" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100644 index 0000000..21147f1 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +base=$1 +version=$2 +mkdir -p "$base/releases"; +cd "$base/dist/"; +files=./* +for file in $files +do + echo "working on folder -> $file" + package=$(ls "./$file" | sed "s/.js$/$version.zip/"); + echo "pacakge is $package"; + cp "$base/README.md" "$base/dist/$file/"; + cp "$base/LICENSE" "$base/dist/$file/"; + zip -r "$package" "$file/"; + mv "$package" "$base/releases/" +done; +cd $base; diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100644 index 0000000..a12f538 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,2 @@ +base=$1 +node "$base/node_modules/.bin/mocha" --require mocha --compilers js:babel-register --reporter spec "$base/src/**/__tests__/*-test.js" diff --git a/scripts/transfer.sh b/scripts/transfer.sh new file mode 100644 index 0000000..2478dca --- /dev/null +++ b/scripts/transfer.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +base=$1 +extensions_dir="$HOME/Library/Containers/com.luckymarmot.Paw/Data/Library/Application Support/com.luckymarmot.Paw/Extensions/" + +mkdir -p "$extensions_dir" +cp -r "$base/dist/" "$extensions_dir" diff --git a/src/DynamicValue.js b/src/DynamicValue.js new file mode 100644 index 0000000..220868c --- /dev/null +++ b/src/DynamicValue.js @@ -0,0 +1,45 @@ +import RandExp from 'randexp' + +import { + registerDynamicValueClass, + InputField +} from './__mocks__/Shims' + +@registerDynamicValueClass +export default class RandomStringDV { + // TODO update static information with correct ones + // Organisation + static organisation = 'luckymarmot' + static repository = 'Paw-StringGeneratorDynamicValue' + + // DynamicValue + static identifier = 'com.luckymarmot.PawExtensions.StringGeneratorDynamicValue' + static title = 'Random String' + + static help = 'https://github.com/luckymarmot/Paw-StringGeneratorDynamicValue' + + static inputs = [ + new InputField('regex', 'Regex', 'String', { persisted: true }), + new InputField( + 'ignoreCase', + 'Ignore case (i)', + 'Checkbox', + { defaultValue: false, persisted: true } + ), + new InputField( + 'multiline', + 'Multiline (m)', + 'Checkbox', + { defaultValue: false, persisted: true } + ) + ] + + // args: context, requests, options + evaluate(context) { + let modifier = '' + modifier += this.ignoreCase ? 'i' : '' + modifier += this.multiline ? 'm' : '' + + return new RandExp(this.regex, modifier).gen() + } +} diff --git a/src/__mocks__/Mocks.js b/src/__mocks__/Mocks.js new file mode 100644 index 0000000..816f4db --- /dev/null +++ b/src/__mocks__/Mocks.js @@ -0,0 +1,64 @@ +export class Mock { + constructor(obj, prefix = '') { + let spies = {} + for (let field in obj) { + if ( + obj.hasOwnProperty(field) && + typeof obj[field] === 'function' + ) { + spies[field] = { + count: 0, + calls: [], + func: obj[field] + } + } + } + + this[prefix + 'spy'] = spies + + const setupFuncSpy = (field) => { + return (...args) => { + this[prefix + 'spy'][field].count += 1 + this[prefix + 'spy'][field].calls.push(args) + return this[prefix + 'spy'][field].func.apply(this, args) + } + } + + for (let field in obj) { + if (obj.hasOwnProperty(field)) { + if (typeof obj[field] === 'function') { + this[field] = setupFuncSpy(field) + } + else { + this[field] = obj[field] + } + } + } + + this[prefix + 'spyOn'] = (field, func) => { + this[prefix + 'spy'][field].func = func + return this + } + + this[prefix + 'getSpy'] = (field) => { + return this[prefix + 'spy'][field] + } + } +} + +export class ClassMock extends Mock { + constructor(instance, prefix = '') { + let properties = Object.getOwnPropertyNames( + Object.getPrototypeOf(instance) + ) + + let obj = {} + for (let property of properties) { + if (property !== 'constructor') { + obj[property] = ::Object.getPrototypeOf(instance)[property] + } + } + + super(obj, prefix) + } +} diff --git a/src/__mocks__/PawMocks.js b/src/__mocks__/PawMocks.js new file mode 100644 index 0000000..ed48800 --- /dev/null +++ b/src/__mocks__/PawMocks.js @@ -0,0 +1,88 @@ +import { Mock } from './Mocks' + +export class PawContextMock extends Mock { + constructor(baseObj, prefix) { + let obj = { + getCurrentRequest: () => {}, + getRequestByName: () => {}, + getRequestGroupByName: () => {}, + getRootRequestTreeItems: () => {}, + getRootRequests: () => {}, + getAllRequests: () => {}, + getAllGroups: () => {}, + getEnvironmentDomainByName: () => {}, + getEnvironmentVariableByName: () => {}, + getRequestById: () => {}, + getRequestGroupById: () => {}, + getEnvironmentDomainById: () => {}, + getEnvironmentVariableById: () => {}, + getEnvironmentById: () => {}, + createRequest: () => {}, + createRequestGroup: () => {}, + createEnvironmentDomain: () => {} + } + Object.assign(obj, baseObj) + super(obj, prefix) + } +} + +export class EnvironmentDomainMock extends Mock { + constructor(baseObj, prefix) { + let obj = { + getEnvironmentByName: () => {}, + getVariableByName: () => {}, + createEnvironment: () => {} + } + Object.assign(obj, baseObj) + super(obj, prefix) + } +} + +export class EnvironmentVariableMock extends Mock { + constructor(baseObj, prefix) { + let obj = { + getCurrentValue: () => {}, + getValue: () => {}, + setCurrentValue: () => {}, + setValue: () => {} + } + Object.assign(obj, baseObj) + super(obj, prefix) + } +} + +export class InputField extends Mock { + constructor(key, name, type, options, prefix = '') { + let obj = { + key: key, + name: name, + type: type, + options: options + } + super(obj, prefix) + } +} + +export class DynamicString extends Mock { + constructor(...items) { + let obj = { + length: null, + components: items, + toString: () => {}, + getComponentAtIndex: () => {}, + getSimpleString: () => {}, + getOnlyString: () => {}, + getOnlyDynamicValue: () => {}, + getEvaluatedString: () => {}, + copy: () => {}, + appendString: () => {}, + appendDynamicValue: () => {}, + appendDynamicString: () => {} + } + super(obj) + } +} + +export const registerDynamicValueClass = (_class) => { + return _class +} diff --git a/src/__mocks__/Shims.js b/src/__mocks__/Shims.js new file mode 100644 index 0000000..fb45170 --- /dev/null +++ b/src/__mocks__/Shims.js @@ -0,0 +1,18 @@ +if ( + typeof registerDynamicValueClass === 'undefined' || + typeof InputField === 'undefined' +) { + let mocks = require('./PawMocks.js') + module.exports = { + registerDynamicValueClass: mocks.registerDynamicValueClass, + InputField: mocks.InputField + } +} +else { + /* eslint-disable no-undef */ + module.exports = { + registerDynamicValueClass: registerDynamicValueClass, + InputField: InputField + } + /* eslint-enable no-undef */ +} diff --git a/src/__mocks__/__tests__/Mocks-test.js b/src/__mocks__/__tests__/Mocks-test.js new file mode 100644 index 0000000..25a6402 --- /dev/null +++ b/src/__mocks__/__tests__/Mocks-test.js @@ -0,0 +1,187 @@ +import { + UnitTest, registerTest, against +} from '../../__utils__/TestUtils' + +import { + Mock +} from '../Mocks' + +@registerTest +@against(Mock) +export class TestMock extends UnitTest { + testSimpleMock() { + let mock = new Mock() + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'spyOn', 'getSpy' ] + ) + this.assertEqual(mock.spy, {}) + } + + testSimpleObjectMock() { + let mock = new Mock({ + a: 12 + }) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'spyOn', 'getSpy' ] + ) + this.assertEqual(mock.spy, {}) + } + + testSimpleObjectWithFuncMock() { + let obj = { + a: (arg) => { + return arg * arg + } + } + + let mock = new Mock(obj) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'spyOn', 'getSpy' ] + ) + this.assertEqual(Object.keys(mock.spy), [ 'a' ]) + } + + testSimpleObjectWithFuncAndVarsMock() { + let obj = { + a: (arg) => { + return arg * arg + }, + b: true + } + + let mock = new Mock(obj) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'b', 'spyOn', 'getSpy' ] + ) + this.assertEqual(Object.keys(mock.spy), [ 'a' ]) + } + + testSpyOn() { + let obj = { + a: (arg) => { + return arg * arg + } + } + + let mock = new Mock(obj) + + mock.spyOn( + 'a', + (arg) => { + return arg * 2 + } + ) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'spyOn', 'getSpy' ] + ) + + let expected = 6 + mock.a(10) + let result = mock.a(3) + this.assertEqual(mock.spy.a.count, 2) + this.assertEqual(mock.spy.a.calls, [ [ 10 ], [ 3 ] ]) + this.assertEqual(result, expected) + } + + testGetSpy() { + let obj = { + a: (arg) => { + return arg * arg + } + } + + let mock = new Mock(obj) + + mock.spyOn( + 'a', + (arg) => { + return arg * 2 + } + ) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'spyOn', 'getSpy' ] + ) + + mock.a(10) + mock.a(3) + + this.assertEqual(mock.getSpy('a').count, 2) + this.assertEqual(mock.getSpy('a').calls, [ [ 10 ], [ 3 ] ]) + } + + testMultipleSpies() { + let obj = { + a: (arg) => { + return arg * arg + }, + b: (key, value) => { + let result = {} + result[value] = key + return result + } + } + + let mock = new Mock(obj) + + mock + .spyOn( + 'a', + (arg) => { + return arg * 2 + } + ) + .spyOn( + 'b', + (k, v) => { + return k + ',' + v + } + ) + + mock.a(10) + mock.b(3, 10) + + this.assertEqual(mock.getSpy('a').count, 1) + this.assertEqual(mock.getSpy('a').calls, [ [ 10 ] ]) + + this.assertEqual(mock.getSpy('b').count, 1) + this.assertEqual(mock.getSpy('b').calls, [ [ 3, 10 ] ]) + } + + testPrefix() { + let obj = { + a: (arg) => { + return arg * arg + }, + b: true + } + + let mock = new Mock(obj, '__') + + this.assertEqual( + Object.keys(mock), + [ '__spy', 'a', 'b', '__spyOn', '__getSpy' ] + ) + this.assertEqual(Object.keys(mock.__spy), [ 'a' ]) + + // no prefix + mock = new Mock(obj, '') + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'a', 'b', 'spyOn', 'getSpy' ] + ) + this.assertEqual(Object.keys(mock.spy), [ 'a' ]) + } +} diff --git a/src/__mocks__/__tests__/PawMocks-test.js b/src/__mocks__/__tests__/PawMocks-test.js new file mode 100644 index 0000000..6387dbd --- /dev/null +++ b/src/__mocks__/__tests__/PawMocks-test.js @@ -0,0 +1,58 @@ +import { UnitTest, registerTest, against } from '../../__utils__/TestUtils' + +import { + PawContextMock, + InputField +} from '../PawMocks' + +@registerTest +@against(PawContextMock) +export class TestPawContextMock extends UnitTest { + testEmptyPawContextMock() { + const pawContextFields = [ + 'getCurrentRequest', + 'getRequestByName', + 'getRequestGroupByName', + 'getRootRequestTreeItems', + 'getRootRequests', + 'getAllRequests', + 'getAllGroups', + 'getEnvironmentDomainByName', + 'getEnvironmentVariableByName', + 'getRequestById', + 'getRequestGroupById', + 'getEnvironmentDomainById', + 'getEnvironmentVariableById', + 'getEnvironmentById', + 'createRequest', + 'createRequestGroup', + 'createEnvironmentDomain' + ] + + const mock = new PawContextMock(null) + + this.assertEqual( + Object.keys(mock), + [ 'spy', ...pawContextFields, 'spyOn', 'getSpy' ] + ) + + this.assertEqual(Object.keys(mock.spy), pawContextFields) + } +} + +@registerTest +@against(InputField) +export class TestInputFieldMock extends UnitTest { + testSimpleInputFieldMock() { + const pawInputFieldFields = [] + + const mock = new InputField('name', 'Name', 'Checkbox', { a: 12 }) + + this.assertEqual( + Object.keys(mock), + [ 'spy', 'key', 'name', 'type', 'options', 'spyOn', 'getSpy' ] + ) + + this.assertEqual(Object.keys(mock.spy), pawInputFieldFields) + } +} diff --git a/src/__tests__/DynamicValue-test.js b/src/__tests__/DynamicValue-test.js new file mode 100644 index 0000000..ecb1f24 --- /dev/null +++ b/src/__tests__/DynamicValue-test.js @@ -0,0 +1,44 @@ +import { + UnitTest, registerTest, against, targets, desc +} from '../__utils__/TestUtils' + +import { + ClassMock +} from '../__mocks__/Mocks' + +import RandomStringDV from '../DynamicValue' + +@registerTest +@against(RandomStringDV) +export class TestRandomStringDV extends UnitTest { + + @targets('evaluate') + @desc('dummy test') + testEvaluateCallsGetSchemaDict() { + const gen = this.__init() + + gen.spyOn('evaluate', () => { + return 42 + }) + + const expected = 42 + const result = gen.evaluate() + + this.assertEqual(gen.spy.evaluate.count, 1) + this.assertEqual(expected, result) + } + + @targets('evaluate') + @desc('tests starting with underscore are ignored') + @desc('@desc decorator is optional, if not used, the name of the func ' + + 'will be used instead') + _testEvaluateCallsMaterializeSchemas() { + // should fail if test is not ignored + this.assertTrue(false) + } + + __init() { + const gen = new ClassMock(new RandomStringDV()) + return gen + } +} diff --git a/src/__utils__/TestUtils.js b/src/__utils__/TestUtils.js new file mode 100644 index 0000000..ab68e55 --- /dev/null +++ b/src/__utils__/TestUtils.js @@ -0,0 +1,214 @@ +import { describe, it, before, after, beforeEach, afterEach } from 'mocha' +import { assert } from 'chai' +import Immutable from 'immutable' + +class UnitTest { + /** + * Override setUpClass + * @return {NULL} NULL + */ + setUpClass() {} + + /** + * Override tearDownClass + * @return {NULL} NULL + */ + tearDownClass() {} + + /** + * Override setUp + * @return {NULL} NULL + */ + setUp() {} + + + /** + * tearDown + * @return {NULL} NULL + */ + tearDown() {} + + + _getTests(test = 'test') { + const prototype = Object.getPrototypeOf(this) + let tests = {} + + for (let fname of Object.getOwnPropertyNames(prototype)) { + if (fname.startsWith(test)) { + const newFnName = this._parseFnName(fname) + + tests[newFnName] = ::Object.getPrototypeOf(this)[fname] + } + } + + return tests + } + + _getSkippedTests() { + return this._getTests('_tests') + } + + _parseFnName(name) { + return name.replace(/^test/, '') + } + + _parseSkippedFnName(name) { + return name.replace(/^_test/, '') + } + + assert(value) { + assert(value) + } + + assertTrue(value, message) { + let warn = (message ? message + '\n' : '') + '\n' + assert.isTrue(value, warn) + } + + assertFalse(value) { + assert.isFalse(value) + } + + assertEqual(a, b, message) { + let A = Immutable.fromJS(a) + let B = Immutable.fromJS(b) + let comparison = Immutable.is(A, B) + let warn = (message ? message + '\n' : '') + `\n${A} \n!== \n${B}` + assert.isTrue(comparison, warn) + } + + assertJSONEqual(a, b, message) { + let A = JSON.stringify(a, null, ' ') + let B = JSON.stringify(b, null, ' ') + this.assertEqual(A, B, message) + } + + assertNotEqual(a, b, message) { + let A = Immutable.fromJS(a) + let B = Immutable.fromJS(b) + let comparison = Immutable.is(A, B) + let warn = (message ? message + '\n' : '') + `\n${A} \n!== \n${B}` + assert.isFalse(comparison, warn) + } + + assertEqualElements(a, b) { + assert.isTrue(Immutable.is( + Immutable.Set(Immutable.fromJS(a)), + Immutable.Set(Immutable.fromJS(b))), + `${a} !== ${b}`) + } + + assertNotNull(value) { + this.assertTrue(value !== null) + } + + assertNull(value) { + this.assertTrue(value === null) + } + + assertIsInstanceOf(value, cls) { + assert.isTrue(value instanceof cls, `${value} !instanceof ${cls}`) + } +} + +function registerTest(Class) { + const test = new Class() + const tests = test._getTests() + const skipped = test._getSkippedTests() + + function skip(_skipped) { + return () => { + for (let _skip of _skipped) { + it(_skip, () => {}) + } + } + } + + describe(Class.name.replace(/^Test/, ''), () => { + before(test.setUpClass.bind(test)) + after(test.tearDownClass.bind(test)) + beforeEach(test.setUp.bind(test)) + afterEach(test.tearDown.bind(test)) + + for (let fname of Object.keys(tests)) { + if (typeof tests[fname] === 'function') { + let name = tests[fname].__desc || fname + it(name, tests[fname].bind(test)) + } + else { + describe.skip(fname, skip(tests[fname])) + } + } + }) + + describe.skip(Class.name.replace(/^Test/, ''), () => { + for (let fname of Object.keys(skipped)) { + if (typeof skipped[fname] === 'function') { + it(fname, skipped[fname].bind(test)) + } + else { + describe(fname, skip(skipped[fname])) + } + } + }) +} + +function targets(name) { + return function(target, key, descriptor) { + if (!target.constructor.__targets) { + target.constructor.__targets = {} + } + if (!target.constructor.__targets[name]) { + target.constructor.__targets[name] = [] + } + target.constructor.__targets[name].push(key) + return descriptor + } +} + +function against(Against, ignores = [ 'constructor' ]) { + return function(Class) { + Class.__against = Against + Class.prototype.testAllMethodsAreTested = () => { + const names = Object.getOwnPropertyNames( + Object.getPrototypeOf(new Against()) + ) + + let missingTests = [] + for (let name of names) { + if ( + ignores.indexOf(name) < 0 && + ( + typeof Class.__targets === 'undefined' || + typeof Class.__targets[name] === 'undefined' + ) + ) { + missingTests.push(name) + } + } + + let superfluousTests = [] + for (let target of Object.keys(Class.__targets || {})) { + if (names.indexOf(target) < 0) { + superfluousTests.push(Class.__targets[target]) + } + } + + const test = new Class() + test.assertEqual(missingTests, [], 'Some methods are not tested') + test.assertEqual(superfluousTests, [], + 'some non-existing methods are being tested' + ) + } + return Class + } +} + +function desc(description) { + return function(target, key, descriptor) { + descriptor.value.__desc = description + return descriptor + } +} + +export { UnitTest, registerTest, targets, against, desc } diff --git a/webpack.config.babel.js b/webpack.config.babel.js new file mode 100644 index 0000000..48a36d7 --- /dev/null +++ b/webpack.config.babel.js @@ -0,0 +1,39 @@ +import webpack from 'webpack' +import path from 'path' +import DynamicValue from './src/DynamicValue' + +if (!DynamicValue.identifier) { + let msg = 'DynamicValue requires an identifier like: ' + + 'com.luckymarmot.PawExtensions.MySuperDV' + throw new Error(msg) +} + +const name = DynamicValue.identifier.split('.').slice(-1) + +const production = process.env.NODE_ENV === 'production'; + +const config = { + target: 'node-webkit', + entry: [ + './src/DynamicValue.js' + ], + output:{ + path: path.join(__dirname, './dist/' + DynamicValue.identifier), + pathInfo: true, + publicPath: '/dist/', + filename: name + '.js' + }, + module: { + loaders: [ + { + loader: 'babel-loader', + include: [ + path.resolve(__dirname, 'src') + ], + test: /\.js$/ + } + ] + } +}; + +module.exports = config;