diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 44491d3..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ad81109 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "parser": "babel-eslint", + "extends": [ + "standard" + ], + "env": { + "jasmine": true + }, + "rules": { + // overrides of the standard style + "curly": [2, "all"], + "indent": [2, 4], + "max-len": [2, 100, 4], + "semi": [2, "always"], + "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], + "wrap-iife": [2, "outside"] + } +} diff --git a/.gitignore b/.gitignore index 7bf6eb1..44d646d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -bower_components node_modules +dist/ diff --git a/.travis.yml b/.travis.yml index 29582fa..9ce799e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,20 @@ +sudo: false language: node_js +cache: + directories: + - node_modules +notifications: + email: false node_js: - - "0.10" -before_script: - - npm install -g bower - - bower install + - '4' before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" + - npm i -g npm@^2.0.0 +before_script: + - npm prune + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +after_success: + - npm run semantic-release +branches: + except: + - "/^v\\d+\\.\\d+\\.\\d+$/" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48ae0b5..9a7ebc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. +By contributing to this repository, including using the issue tracker, you agree to adhere to Twitter's [Open Source Code of Conduct][coc]. + ## Using the issue tracker diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 0c48074..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = function (grunt) { - - require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); - - grunt.initConfig({ - bump: { - options: { - files: [ - 'package.json', - 'bower.json' - ], - commit: true, - commitMessage: 'v%VERSION%', - commitFiles: ['-a'], - createTag: true, - tagName: 'v%VERSION%', - tagMessage: 'v%VERSION%', - push: true, - pushTo: 'origin', - gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' - } - } - }); -}; diff --git a/README.md b/README.md index e5eb115..45c66a7 100644 --- a/README.md +++ b/README.md @@ -7,38 +7,18 @@ A [Flight](https://github.com/flightjs/flight) mixin for filtering, transforming ## Installation ```bash -bower install --save flight-with-observe +npm install --save flight-with-observe ``` -## Example - - - - -## API - - ## Development -Development of this component requires [Bower](http://bower.io) to be globally -installed: +To develop this module, clone the repository and run: -```bash -npm install -g bower ``` - -Then install the Node.js and client-side dependencies by running the following -commands in the repo's root directory. - -```bash -npm install & bower install +$ npm install && npm test ``` -To continuously run the tests in Chrome during development, just run: - -```bash -npm run watch-test -``` +If the tests pass, you have a working environment. You shouldn't need any external dependencies. ## Contributing to this project diff --git a/bower.json b/bower.json deleted file mode 100644 index 4e8d695..0000000 --- a/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "flight-with-observe", - "main": "lib/with-observe.js", - "dependencies": { - "flight": "^1.2.0", - "rxjs": "^2.3.0" - }, - "devDependencies": { - "jasmine-flight": "latest", - "jasmine-jquery": "~2.1.0" - }, - "ignore": [ - ".gitignore", - ".gitattributes", - ".travis.yml", - "CONTRIBUTING.md", - "CHANGELOG.md", - "test", - "package.json", - "karma.conf.js" - ] -} diff --git a/config/constants.js b/config/constants.js new file mode 100644 index 0000000..f4125af --- /dev/null +++ b/config/constants.js @@ -0,0 +1,9 @@ +var path = require('path'); + +var ROOT_DIRECTORY = path.resolve(__dirname, '..'); +var BUILD_DIRECTORY = path.resolve(ROOT_DIRECTORY, 'dist'); + +module.exports = { + ROOT_DIRECTORY: ROOT_DIRECTORY, + BUILD_DIRECTORY: BUILD_DIRECTORY +}; diff --git a/config/karma.config.js b/config/karma.config.js new file mode 100644 index 0000000..cc76bc8 --- /dev/null +++ b/config/karma.config.js @@ -0,0 +1,47 @@ +'use strict'; + +var constants = require('./constants'); +var webpackConfig = require('./webpack.config.test'); +// entry is determined by karma config 'files' array +webpackConfig.entry = {}; + +module.exports = function (config) { + config.set({ + basePath: constants.ROOT_DIRECTORY, + browsers: [ process.env.TRAVIS ? 'Firefox' : 'Chrome' ], + browserNoActivityTimeout: 60000, + client: { + captureConsole: true, + useIframe: true + }, + files: [ + 'node_modules/jquery/dist/jquery.min.js', + 'src/specs.context.js' + ], + frameworks: [ + 'jasmine' + ], + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine', + 'karma-sourcemap-loader', + 'karma-webpack' + ], + preprocessors: { + 'src/specs.context.js': [ 'webpack', 'sourcemap' ] + }, + reporters: [ 'dots' ], + singleRun: true, + webpack: webpackConfig, + webpackMiddleware: { + stats: { + assetsSort: 'name', + colors: true, + children: false, + chunks: false, + modules: false + } + } + }); +}; diff --git a/config/webpack.config.js b/config/webpack.config.js new file mode 100644 index 0000000..646b086 --- /dev/null +++ b/config/webpack.config.js @@ -0,0 +1,46 @@ +var webpack = require('webpack'); + +var DedupePlugin = webpack.optimize.DedupePlugin; +var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin; +var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; + +var plugins = [ + new DedupePlugin(), + new OccurenceOrderPlugin() +]; + +if (process.env.NODE_ENV === 'publish') { + plugins.push( + new UglifyJsPlugin({ + compress: { + dead_code: true, + drop_console: true, + screw_ie8: true, + warnings: true + } + }) + ); +} + +module.exports = { + entry: './src', + module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'babel-loader' + } + ] + }, + resolve: { + alias: { + flight: 'flightjs' + } + }, + output: { + path: './dist', + filename: 'flight-with-observe.js' + }, + plugins: plugins +}; diff --git a/config/webpack.config.publish.js b/config/webpack.config.publish.js new file mode 100644 index 0000000..436fba5 --- /dev/null +++ b/config/webpack.config.publish.js @@ -0,0 +1,14 @@ +var constants = require('./constants'); +var baseConfig = require('./webpack.config'); + +module.exports = Object.assign(baseConfig, { + output: { + library: 'withObserve', + filename: 'flight-with-observe.js', + libraryTarget: 'umd', + path: constants.BUILD_DIRECTORY + }, + externals: [ + 'rx' + ] +}); diff --git a/config/webpack.config.test.js b/config/webpack.config.test.js new file mode 100644 index 0000000..fd13dad --- /dev/null +++ b/config/webpack.config.test.js @@ -0,0 +1,5 @@ +var baseConfig = require('./webpack.config'); + +module.exports = Object.assign(baseConfig, { + devtool: 'inline-source-map' +}); diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 2e866cd..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,71 +0,0 @@ -// Karma configuration file -// -// For all available config options and default values, see: -// https://github.com/karma-runner/karma/blob/stable/lib/config.js#L54 - -module.exports = function (config) { - 'use strict'; - - config.set({ - // base path, that will be used to resolve files and exclude - basePath: '', - - frameworks: [ - 'jasmine', - 'requirejs' - ], - - // list of files / patterns to load in the browser - files: [ - // loaded without require - 'bower_components/jquery/dist/jquery.js', - 'bower_components/jasmine-jquery/lib/jasmine-jquery.js', - 'bower_components/jasmine-flight/lib/jasmine-flight.js', - - // loaded with require - {pattern: 'bower_components/flight/**/*.js', included: false}, - {pattern: 'bower_components/rxjs/dist/rx.lite.min.js', included: false}, - {pattern: 'lib/**/*.js', included: false}, - {pattern: 'test/spec/**/*.spec.js', included: false}, - - 'test/test-main.js' - ], - - // use dots reporter, as travis terminal does not support escaping sequences - // possible values: 'dots', 'progress' - // CLI --reporters progress - reporters: ['progress'], - - // enable / disable watching file and executing tests whenever any file changes - // CLI --auto-watch --no-auto-watch - autoWatch: true, - - // Start these browsers, currently available: - // - Chrome - // - ChromeCanary - // - Firefox - // - Opera - // - Safari (only Mac) - // - PhantomJS - // - IE (only Windows) - // CLI --browsers Chrome, Firefox, Safari - browsers: [ - 'Chrome' - ], - - // If browser does not capture in given timeout [ms], kill it - // CLI --capture-timeout 5000 - captureTimeout: 20000, - - // Auto run tests on start (when browsers are captured) and exit - // CLI --single-run --no-single-run - singleRun: false, - - plugins: [ - 'karma-jasmine', - 'karma-requirejs', - 'karma-chrome-launcher', - 'karma-firefox-launcher' - ] - }); -}; diff --git a/lib/with-observe.js b/lib/with-observe.js deleted file mode 100644 index dd84cb3..0000000 --- a/lib/with-observe.js +++ /dev/null @@ -1,40 +0,0 @@ -define(function (require) { - 'use strict'; - - /** - * with-observe uses RXJS to implement its observer/observable - * patterns. - * https://github.com/Reactive-Extensions/RxJS - */ - var Rx = require('rxjs'); - - return withObserve; - function withObserve() { - /* jshint validthis: true */ - - this.before('initialize', function () { - this.localSubscriptions = []; - }); - - /** - * Observe a sequence which can be disposed of on teardown. - * - * Takes the sequence to observe. - * Returns the observable sequence. - */ - this.observe = function (upstream) { - return Rx.Observable.create(function (o) { - var subscription = upstream.subscribe(o); - // Store the subscription so that the mixin can dispose on teardown. - this.localSubscriptions.push(subscription); - return subscription; - }.bind(this), upstream); - }; - - this.before('teardown', function () { - this.localSubscriptions.forEach(function (subscription) { - subscription.dispose(); - }); - }); - } -}); diff --git a/package.json b/package.json index e55a3d2..39ecfe7 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,52 @@ { "name": "flight-with-observe", - "version": "1.0.0", - "devDependencies": { - "grunt": "~0.4.5", - "grunt-bump": "latest", - "karma": "~0.12.6", - "karma-cli": "0.0.4", - "karma-jasmine": "~0.2.0", - "karma-requirejs": "~0.2.2", - "karma-chrome-launcher": "*", - "karma-firefox-launcher": "*", - "matchdep": "latest" - }, + "description": "A Flight mixin for locally subscribing to an observable sequence.", + "main": "dist/flight-with-observe.js", + "files": [ + "dist" + ], "scripts": { - "test": "karma start --single-run --reporters dots --browsers Firefox", - "watch-test": "karma start" + "build": "rm -rf ./dist && NODE_ENV=publish webpack --config config/webpack.config.publish.js --sort-assets-by --progress", + "lint": "eslint config src", + "lint:fix": "eslint --fix config src", + "prepublish": "npm run build", + "specs": "NODE_ENV=test karma start config/karma.config.js", + "specs:watch": "npm run specs -- --no-single-run", + "test": "npm run specs && npm run lint", + "semantic-release": "semantic-release pre && npm publish && semantic-release post" }, - "description": "A Flight mixin for locally subscribing to an observable sequence.", - "main": "lib/with-observe.js", - "directories": { - "test": "test" + "devDependencies": { + "babel-core": "^5.8.24", + "babel-eslint": "^4.1.1", + "babel-loader": "^5.3.2", + "babel-plugin-typecheck": "^1.2.0", + "babel-runtime": "^5.8.20", + "chai": "^3.2.0", + "eslint": "^1.3.1", + "eslint-config-standard": "^4.3.1", + "eslint-config-standard-react": "^1.0.4", + "eslint-plugin-react": "^3.3.1", + "eslint-plugin-standard": "^1.3.0", + "flightjs": "^1.5.1", + "immutable": "^3.7.5", + "jasmine-core": "^2.3.4", + "jquery": "^2.1.4", + "karma": "^0.13.9", + "karma-chrome-launcher": "^0.2.0", + "karma-cli": "^0.1.0", + "karma-firefox-launcher": "^0.1.6", + "karma-jasmine": "^0.3.6", + "karma-mocha": "^0.2.0", + "karma-sourcemap-loader": "^0.3.5", + "karma-webpack": "^1.7.0", + "mocha": "^2.3.2", + "object-assign": "^4.0.1", + "rx": "^4", + "semantic-release": "^4.3.5", + "webpack": "^1.12.1" + }, + "peerDependencies": { + "rx": "^4" }, "repository": { "type": "git", @@ -31,6 +58,7 @@ "state", "observable", "rxjs", + "rx", "flight-toolbox" ], "contributors": [ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..20044f9 --- /dev/null +++ b/src/index.js @@ -0,0 +1,34 @@ + +/** + * with-observe uses RXJS to implement its observer/observable + * patterns. + * https://github.com/Reactive-Extensions/RxJS + */ +import Rx from 'rx'; + +export default function withObserve() { + this.before('initialize', function () { + this.localSubscriptions = []; + }); + + /** + * Observe a sequence which can be disposed of on teardown. + * + * Takes the sequence to observe. + * Returns the observable sequence. + */ + this.observe = function (upstream) { + return Rx.Observable.create((o) => { + var subscription = upstream.subscribe(o); + // Store the subscription so that the mixin can dispose on teardown. + this.localSubscriptions.push(subscription); + return subscription; + }, upstream); + }; + + this.before('teardown', function () { + this.localSubscriptions.forEach(function (subscription) { + subscription.dispose(); + }); + }); +} diff --git a/src/specs.context.js b/src/specs.context.js new file mode 100644 index 0000000..0c285e5 --- /dev/null +++ b/src/specs.context.js @@ -0,0 +1,14 @@ +/** + * Since we use webpack-specific features in our modules (e.g., loaders, + * plugins, adding CSS to the dependency graph), we must use webpack to build a + * test bundle. + * + * This module creates a context of all the unit test files (as per the unit + * test naming convention). It's used as the webpack entry file for unit tests. + * + * See: https://github.com/webpack/docs/wiki/context + */ + +const specsContext = require.context('.', true, /.+\.spec\.js$/); +specsContext.keys().forEach(specsContext); +module.exports = specsContext; diff --git a/src/with-observe.spec.js b/src/with-observe.spec.js new file mode 100644 index 0000000..5d0ffd3 --- /dev/null +++ b/src/with-observe.spec.js @@ -0,0 +1,42 @@ +import Rx from 'rx'; +import { component } from 'flight'; +import withObserve from '.'; + +describe('withObserve', function () { + var subject; + var observable; + + beforeEach(function () { + subject = new Rx.BehaviorSubject(1); + observable = subject.asObservable(); + const Component = component(function Base() {}, withObserve); + this.component = (new Component()).initialize(document.body); + }); + + it('should observe changed values', function (done) { + this.component.observe(observable).subscribeOnNext(function (value) { + if (value === 2) { + done(); + } + }); + subject.onNext(2); + }); + + it('should dispose of observables on teardown', function () { + this.component.observe(observable); + + var called = 0; + this.component.observe(observable).subscribeOnNext(function (value) { + called = called + 1; + }); + expect(called).toBe(1); + + subject.onNext('still subscribed'); + expect(called).toBe(2); + + // Teardown the component and check handler is not called again. + this.component.teardown(); + subject.onNext('not subscribed'); + expect(called).toBe(2); + }); +}); diff --git a/test/spec/with-observe.spec.js b/test/spec/with-observe.spec.js deleted file mode 100644 index 3ad2c5a..0000000 --- a/test/spec/with-observe.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -define(function (require) { - 'use strict'; - - var Rx = require('rxjs'); - - describeMixin('lib/with-observe', function () { - - var subject; - var observable; - - beforeEach(function () { - subject = new Rx.BehaviorSubject(1); - observable = subject.asObservable(); - this.setupComponent(); - }); - - it('should observe changed values', function (done) { - this.component.observe(observable).subscribeOnNext(function (value) { - if (value === 2) { - done(); - } - }); - subject.onNext(2); - }); - - it('should dispose of observables on teardown', function () { - this.component.observe(observable); - - var called = 0; - this.component.observe(observable).subscribeOnNext(function (value) { - called = called + 1; - }); - expect(called).toBe(1); - - subject.onNext('still subscribed'); - expect(called).toBe(2); - - // Teardown the component and check handler is not called again. - this.component.teardown(); - subject.onNext('not subscribed'); - expect(called).toBe(2); - - }); - }); -}); diff --git a/test/test-main.js b/test/test-main.js deleted file mode 100644 index 5769565..0000000 --- a/test/test-main.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var tests = Object.keys(window.__karma__.files).filter(function (file) { - return (/\.spec\.js$/.test(file)); -}); - -requirejs.config({ - // Karma serves files from '/base' - baseUrl: '/base', - - paths: { - 'flight': 'bower_components/flight', - 'rxjs': 'bower_components/rxjs/dist/rx.lite.min' - }, - - // ask Require.js to load these files (all our tests) - deps: tests, - - // start test run, once Require.js is done - callback: window.__karma__.start -});