├── .editorconfig ├── .gitignore ├── .travis.yml ├── README.md ├── angular-example ├── .gitignore ├── README.md ├── app │ ├── app.html │ ├── app.module.ts │ ├── app.ts │ ├── bootstrap.ts │ ├── models │ │ └── todo.ts │ └── services │ │ ├── localStorage.ts │ │ └── store.ts ├── bs-config.json ├── index.html ├── intern.json ├── package-lock.json ├── package.json ├── systemjs-angular-loader.ts ├── systemjs.config.ts ├── tests │ ├── app.ts │ ├── functional │ │ └── Todo.ts │ ├── index.html │ ├── intern-angular-shim.ts │ ├── services │ │ └── store.ts │ ├── systemjs-istanbuljs.ts │ └── systemjs.config.ts └── tsconfig.json ├── angularjs-example ├── README.md ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ └── todoCtrl.js │ ├── directives │ │ ├── todoBlur.js │ │ ├── todoEscape.js │ │ └── todoFocus.js │ └── services │ │ └── todoStorage.js ├── package.json └── tests │ ├── all.js │ ├── controllers │ └── todoCtrl.js │ ├── directives │ └── todoBlurFocus.js │ ├── functional │ └── Todo.js │ └── intern.js ├── backbone-example ├── README.md ├── index.html ├── js │ ├── app.js │ ├── collections │ │ └── todos.js │ ├── models │ │ └── todo.js │ ├── routers │ │ └── router.js │ └── views │ │ ├── app-view.js │ │ └── todo-view.js ├── package.json └── tests │ ├── all.js │ ├── functional │ └── Todo.js │ ├── intern.js │ └── models │ └── todo.js ├── dojo-example ├── README.md ├── css │ └── app.css ├── index.html ├── intern.json ├── js │ └── todo │ │ ├── CssToggleWidget.js │ │ ├── TodoList.js │ │ ├── app.html │ │ ├── app.js │ │ ├── app18.html │ │ ├── app18.js │ │ ├── ctrl │ │ ├── RouteController.js │ │ ├── TodoListRefController.js │ │ ├── TodoRefController.js │ │ └── _HashCompletedMixin.js │ │ ├── form │ │ ├── CheckBox.js │ │ └── InlineEditBox.js │ │ ├── misc │ │ ├── HashSelectedConverter.js │ │ └── LessThanOrEqualToConverter.js │ │ ├── model │ │ ├── SimpleTodoModel.js │ │ └── TodoModel.js │ │ └── store │ │ └── LocalStorage.js ├── package-lock.json ├── package.json └── tests │ ├── all.js │ ├── form │ └── CheckBox.js │ ├── functional │ └── Todo.js │ ├── model │ └── SimpleTodoModel.js │ └── store │ └── LocalStorage.js ├── electron-example ├── .babelrc ├── .gitignore ├── README.md ├── intern.json ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── actions │ │ └── index.js │ ├── bootstrap.js │ ├── components │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── MainSection.js │ │ ├── TodoItem.js │ │ └── TodoTextInput.js │ ├── constants │ │ ├── ActionTypes.js │ │ └── TodoFilters.js │ ├── containers │ │ └── App.js │ ├── index.js │ ├── main.js │ ├── reducers │ │ ├── index.js │ │ └── todos.js │ └── renderer.ts └── tests │ ├── build_check.js │ ├── functional │ ├── app.js │ └── spectron.js │ ├── pre.js │ └── unit │ └── actions │ └── index.js ├── grunt-example ├── Gruntfile.js ├── README.md ├── app │ ├── Block.js │ └── index.html ├── package-lock.json ├── package.json └── tests │ └── lib │ ├── add.js │ └── get.js ├── jquery-example ├── README.md ├── css │ └── app.css ├── index.html ├── intern.json ├── js │ ├── app.js │ └── utils.js ├── package-lock.json ├── package.json └── tests │ ├── functional │ └── Todo.js │ └── utils.js ├── parallel-example ├── README.md ├── index.html ├── intern.js ├── package.json ├── parallel.sh └── tests │ └── functional │ ├── sample1.js │ └── sample2.js ├── phantomjs-example ├── README.md ├── package.json └── tests │ ├── intern.js │ └── test_example.js ├── react-enzyme-example ├── .babelrc ├── .gitignore ├── README.md ├── intern.json ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── actions │ │ └── index.js │ ├── components │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── MainSection.js │ │ ├── TodoItem.js │ │ └── TodoTextInput.js │ ├── constants │ │ ├── ActionTypes.js │ │ └── TodoFilters.js │ ├── containers │ │ └── App.js │ ├── index.js │ └── reducers │ │ ├── index.js │ │ └── todos.js └── tests │ ├── build_check.js │ ├── functional │ └── Todo.js │ └── unit │ ├── actions │ └── index.js │ ├── components │ └── Header.js │ └── reducers │ └── todos.js ├── react-example ├── README.md ├── index.html ├── js │ ├── app.jsx │ ├── footer.jsx │ ├── todoItem.jsx │ ├── todoModel.js │ └── utils.js ├── package.json └── tests │ ├── all.js │ ├── functional │ └── Todo.js │ ├── intern.js │ ├── support │ └── jsx.js │ └── todoModel.js ├── run_all.sh ├── travis-ci-example ├── README.md ├── app │ └── App.js ├── package.json └── tests │ ├── intern.js │ └── lib │ └── demo.js ├── typescript-example ├── .gitignore ├── README.md ├── index.html ├── intern.json ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ ├── collections │ │ └── todos.ts │ ├── config.ts │ ├── models │ │ └── todo.ts │ ├── routers │ │ └── router.ts │ └── views │ │ ├── app-view.ts │ │ └── todo-view.ts ├── tests │ ├── functional │ │ └── Todo.ts │ ├── tsconfig.json │ └── unit │ │ ├── models │ │ └── todo.ts │ │ └── routers │ │ └── router.ts └── tsconfig.json └── webpack-example ├── .gitignore ├── README.md ├── index.html ├── intern.json ├── package-lock.json ├── package.json ├── src ├── app.js ├── collections │ └── todos.js ├── globals.d.ts ├── models │ └── todo.ts ├── routers │ └── router.ts └── views │ ├── app-view.js │ └── todo-view.js ├── tests ├── all.js ├── functional │ └── Todo.js ├── intern.js ├── models │ └── todo.js ├── routers │ └── router.js └── tests.js ├── tsconfig.json └── webpack.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.md] 4 | indent_style = space 5 | max_line_length = 120 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sauce_connect.log* 2 | node_modules 3 | html-report 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.5.0' 4 | script: cd travis-ci-example && npm install && npm test 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # intern-examples 2 | 3 | [![Intern](https://theintern.io/images/intern-v3.svg)](https://github.com/theintern/intern/tree/3.4/) 4 | [![Intern](https://theintern.io/images/intern-v4.svg)](https://github.com/theintern/intern/tree/master/) 5 | ![](https://api.travis-ci.org/theintern/intern-examples.svg?branch=master) 6 | 7 | 8 |

Intern logo


9 | 10 | [Intern](https://github.com/theintern/intern) is a complete test system for JavaScript designed to help you write and run consistent, high-quality test cases for your JavaScript libraries and applications. It can be used to test _any_ JavaScript code. 11 | 12 | 13 | This repository is a collection of examples of using Intern in web applications. Use these examples as your guide to integrate Intern into your projects! Every example has a README that will guide you through the process of setting it up. 14 | 15 | ## Intern 4 Examples 16 | 17 | * [Dojo](./dojo-example) 18 | * [Electron](./electron-example) 19 | * [jQuery](./jquery-example) 20 | * [React with Enzyme](./react-enzyme-example) 21 | * [TypeScript](./typescript-example) 22 | * [Grunt](./grunt-example) 23 | 24 | ## Intern 3 Examples 25 | 26 | Each of the examples can be switched from running tests locally to using a cloud testing provider by setting the relevant [Cloud testing Intern settings](https://theintern.github.io/intern/#hosted-selenium) within the Intern config for that example. 27 | 28 | * [Backbone](./backbone-example) 29 | * [React](./react-example) 30 | * [Parallelized Intern](./parallel-example) 31 | * [Travis CI](./travis-ci-example) 32 | 33 | ## External Examples 34 | 35 | Intern 4 hasn’t been out for very long yet, so most of these are still based on Intern 3. 36 | 37 | * [Cassowary JS](https://github.com/slightlyoff/cassowary.js/) 38 | * [jQuery PEP](https://github.com/jquery/PEP/tree/master/tests) 39 | * [Official Intern Tutorial](https://github.com/theintern/intern-tutorial) 40 | * [Tutorial from ArcGIS](https://github.com/stdavis/intern-tutorial-esri-jsapi) 41 | * [Firefox Accounts JS Client testing example](https://github.com/mozilla/fxa-js-client/tree/master/tests) 42 | 43 | ## Contributing 44 | 45 | We welcome contributions of new examples, or improvements/updates to existing examples. Just fork this repo, add your example to a new branch, and make a PR. Note that like most open source projects, we require everyone to sign a [contributor license agreement](https://js.foundation/CLA/) when making non-trivial PRs. 46 | 47 | 48 | © [SitePen, Inc.](http://sitepen.com) and its [contributors](https://github.com/theintern/intern/graphs/contributors) 49 | 50 | -------------------------------------------------------------------------------- /angular-example/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.js 3 | *.js.map 4 | typings 5 | -------------------------------------------------------------------------------- /angular-example/README.md: -------------------------------------------------------------------------------- 1 | angular2-example 2 | ================ 3 | 4 | This example is based on the Angular 2 TodoMVC implementation by [Sam Saccone](http://github.com/samccone). 5 | 6 | ## Setup 7 | 8 | 1. Install the JRE or JDK. This demo uses Selenium, which requires Java, to run WebDriver tests. 9 | 10 | 2. Install node modules 11 | ``` 12 | $ npm install 13 | ``` 14 | 15 | 3. Build the example 16 | ``` 17 | $ npm run build 18 | ``` 19 | 20 | ## Running tests 21 | 22 | **Unit tests in Node** 23 | 24 | $ npm test 25 | 26 | **WebDriver tests** 27 | 28 | $ npm test webdriver 29 | -------------------------------------------------------------------------------- /angular-example/app/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

todos

4 | 5 |
6 |
7 | 8 | 21 |
22 | 26 |
27 | -------------------------------------------------------------------------------- /angular-example/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { LocalStorageService, LocalStorageServiceKey } from './services/localStorage'; 6 | import { TodoService } from './services/store'; 7 | import { TodoApp } from './app'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | BrowserModule, 12 | FormsModule 13 | ], 14 | declarations: [ TodoApp ], 15 | providers: [ 16 | { provide: LocalStorageServiceKey, useValue: 'angular-todos' }, 17 | LocalStorageService, 18 | TodoService 19 | ], 20 | bootstrap: [ TodoApp ] 21 | }) 22 | export class AppModule { } 23 | -------------------------------------------------------------------------------- /angular-example/app/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TodoService } from './services/store'; 3 | import { Todo } from './models/todo'; 4 | 5 | @Component({ 6 | selector: 'todo-app', 7 | templateUrl: './app.html' 8 | }) 9 | export class TodoApp { 10 | private todoStore: TodoService; 11 | private todos: Todo[] = []; 12 | 13 | private remaining = 0; 14 | private completed = 0; 15 | 16 | newTodoText = ''; 17 | 18 | constructor(todoStore: TodoService) { 19 | this.todoStore = todoStore; 20 | } 21 | 22 | ngOnInit() { 23 | this.todoStore.get().subscribe(todos => this.initialize(todos)); 24 | } 25 | 26 | private initialize(todos: Todo[]) { 27 | this.todos = todos; 28 | 29 | this.remaining = 0; 30 | this.completed = 0; 31 | 32 | todos.forEach(todo => { 33 | if (todo.completed) { 34 | this.completed++; 35 | } else { 36 | this.remaining++; 37 | } 38 | }); 39 | } 40 | 41 | setAllTo(value: boolean) { 42 | this.todoStore.set( 43 | this.todos.map(todo => { 44 | todo.completed = value; 45 | return todo; 46 | }) 47 | ) 48 | .subscribe(todos => this.initialize(todos)) 49 | ; 50 | } 51 | 52 | cancelEditingTodo(todo: Todo) { 53 | todo.editing = false; 54 | } 55 | 56 | updateEditingTodo(todo: Todo, editedTitle: string) { 57 | editedTitle = editedTitle.trim(); 58 | todo.editing = false; 59 | 60 | let observable; 61 | if (editedTitle.length === 0) { 62 | observable = this.todoStore.delete(todo); 63 | } else { 64 | todo.title = editedTitle; 65 | observable = this.todoStore.update(todo); 66 | } 67 | 68 | observable.subscribe(todos => this.initialize(todos)); 69 | } 70 | 71 | editTodo(todo: Todo) { 72 | todo.editing = true; 73 | } 74 | 75 | removeCompleted() { 76 | this.todoStore.delete(this.todos.filter(todo => todo.completed)) 77 | .subscribe(todos => this.initialize(todos)) 78 | ; 79 | } 80 | 81 | toggleCompletion(todo: Todo) { 82 | todo.completed = !todo.completed; 83 | this.todoStore.set(this.todos) 84 | .subscribe(todos => this.initialize(todos)) 85 | ; 86 | } 87 | 88 | remove(todo: Todo){ 89 | this.todoStore.delete(todo) 90 | .subscribe(todos => this.initialize(todos)) 91 | ; 92 | } 93 | 94 | addTodo() { 95 | if (this.newTodoText.trim().length) { 96 | this.todoStore.add(this.newTodoText) 97 | .subscribe(todos => this.initialize(todos)) 98 | ; 99 | this.newTodoText = ''; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /angular-example/app/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /angular-example/app/models/todo.ts: -------------------------------------------------------------------------------- 1 | export interface TodoObject { 2 | completed?: boolean; 3 | editing?: boolean; 4 | readonly id?: number; 5 | title: string; 6 | } 7 | 8 | export class Todo implements TodoObject { 9 | readonly id: number; 10 | completed: boolean; 11 | editing: boolean; 12 | title: string; 13 | 14 | constructor({ title, id = Date.now(), editing = false, completed = false }: TodoObject) { 15 | this.id = id; 16 | this.completed = completed; 17 | this.editing = editing; 18 | this.title = title.trim(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /angular-example/app/services/localStorage.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, InjectionToken } from '@angular/core'; 2 | 3 | export const LocalStorageServiceKey = new InjectionToken('LocalStorageServiceKey'); 4 | 5 | @Injectable() 6 | export class LocalStorageService { 7 | constructor(@Inject(LocalStorageServiceKey) private key: string) {} 8 | 9 | get(): T { 10 | return JSON.parse(localStorage.getItem(this.key) || 'null'); 11 | } 12 | 13 | set(value: T) { 14 | localStorage.setItem(this.key, JSON.stringify(value)); 15 | } 16 | 17 | clear() { 18 | localStorage.removeItem(this.key); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /angular-example/app/services/store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Todo, TodoObject } from '../models/todo'; 3 | import { LocalStorageService } from './localStorage'; 4 | 5 | import { Observable } from 'rxjs/Observable'; 6 | import 'rxjs/add/observable/of'; 7 | import 'rxjs/add/operator/map'; 8 | 9 | export { Todo, TodoObject } 10 | 11 | @Injectable() 12 | export class TodoService { 13 | constructor(private storage: LocalStorageService) {} 14 | 15 | get(): Observable { 16 | const objects = this.storage.get() || []; 17 | return Observable.of(objects.map(object => new Todo(object))); 18 | } 19 | 20 | add(title: string): Observable { 21 | const objects = this.storage.get() || []; 22 | objects.push(new Todo({ title })); 23 | 24 | this.storage.set(objects); 25 | 26 | return this.get(); 27 | } 28 | 29 | update(todo: Todo): Observable { 30 | const objects = this.storage.get() || []; 31 | const index = objects.findIndex(object => object.id === todo.id); 32 | 33 | if (index === -1) { 34 | objects.push(todo); 35 | } else { 36 | objects.splice(index, 1, todo); 37 | } 38 | 39 | this.storage.set(objects); 40 | 41 | return this.get(); 42 | } 43 | 44 | set(todos: Todo[]): Observable { 45 | this.storage.set(todos); 46 | return this.get(); 47 | } 48 | 49 | delete(todos: Todo | Todo[]): Observable { 50 | if (!Array.isArray(todos)) { 51 | todos = [ todos ]; 52 | } 53 | 54 | const ids = new Set(todos.map(todo => todo.id)); 55 | const objects = (this.storage.get() || []).filter(todo => !ids.has(todo.id)); 56 | 57 | this.storage.set(objects); 58 | 59 | return this.get(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /angular-example/bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "./**/*.js", 4 | "./intern.json" 5 | ], 6 | "open": false, 7 | "logLevel": "silent", 8 | "logConnections": false, 9 | "server": { 10 | "baseDir": ".", 11 | "middleware": { 12 | "0": null 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /angular-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular2 • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /angular-example/intern.json: -------------------------------------------------------------------------------- 1 | { 2 | "environments": "chrome", 3 | "functionalSuites": "dist/tests/functional/Todo.js", 4 | "browser": { 5 | "loader": { 6 | "script": "systemjs" 7 | }, 8 | "plugins": [ 9 | { 10 | "script": "dist/tests/intern-angular-shim.js", 11 | "useLoader": true, 12 | "options": { 13 | "appConfig": "dist/systemjs.config.js", 14 | "testConfig": "dist/tests/systemjs.config.js", 15 | "require": [ 16 | "node_modules/core-js/client/shim.js", 17 | "node_modules/zone.js/dist/zone.js", 18 | "node_modules/zone.js/dist/long-stack-trace-zone.js" 19 | ] 20 | } 21 | } 22 | ], 23 | "suites": [ 24 | "dist/tests/services/store.js", 25 | "dist/tests/app.js" 26 | ] 27 | }, 28 | "excludeInstrumentation": "(?:node_modules|browser|tests)\\/|dist\\/[^\\/]+\\.js" 29 | } 30 | -------------------------------------------------------------------------------- /angular-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "@angular/common": "~4.3.0", 5 | "@angular/compiler": "~4.3.0", 6 | "@angular/core": "~4.3.0", 7 | "@angular/forms": "~4.3.0", 8 | "@angular/platform-browser": "~4.3.0", 9 | "@angular/platform-browser-dynamic": "~4.3.0", 10 | "@angular/router": "~4.3.0", 11 | "core-js": "~2.4.1", 12 | "rxjs": "~5.4.2", 13 | "systemjs": "0.19.39", 14 | "todomvc-app-css": "2.0.0", 15 | "todomvc-common": "1.0.1", 16 | "tslib": "~2.0.1", 17 | "zone.js": "~0.8.14" 18 | }, 19 | "devDependencies": { 20 | "@types/sinon": "~2.3.3", 21 | "@types/systemjs": "0.20.2", 22 | "concurrently": "~3.5.0", 23 | "cpx": "~1.5.0", 24 | "intern": "^4.8.7", 25 | "lite-server": "~2.3.0", 26 | "sinon": "~2.3.8", 27 | "typescript": "~4.0.2" 28 | }, 29 | "scripts": { 30 | "copy": "cpx \"app/**/*.{html,css}\" dist/app", 31 | "copy:watch": "cpx \"app/**/*.{html,css}\" dist/app --watch", 32 | "compile": "tsc", 33 | "compile:watch": "tsc -w", 34 | "build": "concurrently \"npm run copy\" \"npm run compile\"", 35 | "build:watch": "concurrently \"npm run copy:watch\" \"npm run compile:watch\"", 36 | "pretest": "npm run build", 37 | "test": "intern", 38 | "serve": "lite-server", 39 | "start": "concurrently \"npm run build:watch\" \"npm run serve\"" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /angular-example/systemjs-angular-loader.ts: -------------------------------------------------------------------------------- 1 | const templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm; 2 | const stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; 3 | const stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; 4 | 5 | function commonPath(path1: string[], path2: string[]) { 6 | const length = Math.min(path1.length, path2.length); 7 | 8 | let position: number; 9 | 10 | for (position = 0; position < length; position++) { 11 | if (path1[position] !== path2[position]) { 12 | position--; 13 | break; 14 | } 15 | } 16 | 17 | return path1.slice(0, position + 1); 18 | } 19 | 20 | function relative(from: string[], to: string[]) { 21 | const common = commonPath(from, to); 22 | 23 | to = to.slice(common.length); 24 | if (from.length === common.length) { 25 | return to; 26 | } 27 | 28 | return [...from.slice(common.length).map(_ => '..'), ...to]; 29 | } 30 | 31 | const rootUrlParts = (() => { 32 | const url = document.createElement('a'); 33 | url.href = document.baseURI; 34 | const parts = url.pathname.split('/'); 35 | parts.pop(); 36 | return parts; 37 | })(); 38 | 39 | export function translate(load: { source: string; address: string; }) { 40 | if (load.source.indexOf('moduleId') !== -1) { 41 | return load; 42 | } 43 | 44 | const url = document.createElement('a'); 45 | url.href = load.address; 46 | const urlParts = url.pathname.split('/'); 47 | urlParts.pop(); 48 | 49 | const baseHref = document.createElement('a'); 50 | baseHref.href = this.baseURL; 51 | const baseHrefParts = baseHref.pathname.split('/'); 52 | 53 | const relPath = relative(rootUrlParts, baseHrefParts).join('/'); 54 | 55 | let basePath = urlParts.join('/'); 56 | basePath = basePath.replace(baseHref.pathname, ''); 57 | basePath = `${relPath}${basePath}`; 58 | 59 | load.source = load.source 60 | .replace(templateUrlRegex, function(_, __, resolvedUrl){ 61 | if (resolvedUrl.startsWith('.')) { 62 | resolvedUrl = basePath + resolvedUrl.substr(1); 63 | } 64 | 65 | return `templateUrl: "${resolvedUrl}"`; 66 | }) 67 | .replace(stylesRegex, function(_, relativeUrls) { 68 | const urls = []; 69 | let match: RegExpExecArray; 70 | 71 | while ((match = stringRegex.exec(relativeUrls)) !== null) { 72 | if (match[2].startsWith('.')) { 73 | urls.push(`"${basePath}${match[2].substr(1)}"`); 74 | } else { 75 | urls.push(`"${match[2]}"`); 76 | } 77 | } 78 | 79 | return `styleUrls: [${urls.join(', ')}]`; 80 | }); 81 | 82 | return load; 83 | } 84 | -------------------------------------------------------------------------------- /angular-example/systemjs.config.ts: -------------------------------------------------------------------------------- 1 | SystemJS.config({ 2 | map: { 3 | 'rxjs': 'node_modules/rxjs', 4 | '@angular': 'node_modules/@angular', 5 | 'app': 'dist/app', 6 | 'tslib': 'node_modules/tslib/tslib.js' 7 | }, 8 | // packages tells the System loader how to load when no filename and/or no extension 9 | packages: { 10 | '@angular/common': { main: 'bundles/common.umd.js' }, 11 | '@angular/common/http': { main: '../bundles/common-http.umd.js' }, 12 | '@angular/core': { main: 'bundles/core.umd.js' }, 13 | '@angular/compiler': { main: 'bundles/compiler.umd.js' }, 14 | '@angular/platform-browser': { main: 'bundles/platform-browser.umd.js' }, 15 | '@angular/platform-browser-dynamic': { main: 'bundles/platform-browser-dynamic.umd.js' }, 16 | '@angular/router': { main: 'bundles/router.umd.js' }, 17 | '@angular/forms': { main: 'bundles/forms.umd.js' }, 18 | app: { 19 | main: 'bootstrap.js', 20 | defaultExtension: 'js', 21 | meta: { 22 | './*.js': { 23 | loader: 'dist/systemjs-angular-loader.js' 24 | } 25 | } 26 | }, 27 | rxjs: { 28 | defaultExtension: 'js' 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /angular-example/tests/app.ts: -------------------------------------------------------------------------------- 1 | const { describe, it, beforeEach } = intern.getInterface('bdd'); 2 | const { expect } = intern.getPlugin('chai'); 3 | import { stub, SinonStub, SinonSpyCall } from 'sinon'; 4 | 5 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 6 | import { By } from '@angular/platform-browser'; 7 | import { DebugElement } from '@angular/core'; 8 | 9 | import { TodoApp } from 'app/app'; 10 | import { FormsModule } from '@angular/forms'; 11 | import { TodoService, Todo } from 'app/services/store'; 12 | 13 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 14 | 15 | describe('TodoApp', () => { 16 | let component: TodoApp; 17 | let fixture: ComponentFixture; 18 | let service: { 19 | get: SinonStub; 20 | add: SinonStub; 21 | }; 22 | let objects: Todo[]; 23 | let de: DebugElement; 24 | 25 | beforeEach(async () => { 26 | objects = [ 27 | new Todo({ id: 1, title: 'one' }), 28 | new Todo({ id: 2, title: 'two' }), 29 | new Todo({ id: 3, title: 'three' }) 30 | ]; 31 | 32 | const mockService = { 33 | get: stub().callsFake(() => new BehaviorSubject(objects)), 34 | add: stub().callsFake((newTodo) => { 35 | const newObjects = [...objects]; 36 | newObjects.push(newTodo); 37 | return new BehaviorSubject(newObjects); 38 | }) 39 | }; 40 | 41 | await TestBed.configureTestingModule({ 42 | imports: [ FormsModule ], 43 | declarations: [ 44 | TodoApp 45 | ], 46 | providers: [ 47 | { provide: TodoService, useValue: mockService } 48 | ] 49 | }) 50 | .compileComponents() 51 | ; 52 | 53 | fixture = TestBed.createComponent(TodoApp); 54 | component = fixture.componentInstance; 55 | de = fixture.debugElement; 56 | service = TestBed.get(TodoService); 57 | }); 58 | 59 | it('should create an app', () => { 60 | expect(component).to.be.instanceOf(TodoApp); 61 | }); 62 | 63 | it('should call TodoService#get() on init', () => { 64 | fixture.detectChanges(); 65 | 66 | expect(service.get.called).to.be.true; 67 | }); 68 | 69 | it('should call TodoService#add() when adding a todo', async () => { 70 | fixture.detectChanges(); 71 | 72 | const input = de.query(By.css('.new-todo')); 73 | input.triggerEventHandler('input', { 74 | target: { 75 | value: 'stuff' 76 | } 77 | }); 78 | input.triggerEventHandler('keyup.enter', null); 79 | 80 | await fixture.whenStable(); 81 | fixture.detectChanges(); 82 | 83 | expect(service.add.calledOnce).to.be.true; 84 | expect(service.add.calledWith('stuff')).to.be.true; 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /angular-example/tests/functional/Todo.ts: -------------------------------------------------------------------------------- 1 | const { describe, before, it } = intern.getInterface('bdd'); 2 | const { expect } = intern.getPlugin('chai'); 3 | import keys from '@theintern/leadfoot/keys'; 4 | 5 | describe('functional/Todo', () => { 6 | before(async ({ remote }) => { 7 | await remote.get('index.html'); 8 | await remote.setFindTimeout(5000); 9 | await remote.findDisplayedByCssSelector('todo-app'); 10 | }); 11 | 12 | it('should submit form', async ({ remote }) => { 13 | 14 | const input = remote.findByCssSelector('input.new-todo'); 15 | await input.click(); 16 | await input.type('Task 1'); 17 | await input.type(keys.ENTER); 18 | await input.type('Task 2'); 19 | await input.type(keys.ENTER); 20 | await input.type('Task 3'); 21 | await input.type(keys.ENTER); 22 | 23 | const todos = await remote.findAllByCssSelector('.todo-list > li'); 24 | expect(todos).to.have.lengthOf(3); 25 | 26 | expect(await todos[0].getVisibleText()).to.equal('Task 1'); 27 | expect(await todos[1].getVisibleText()).to.equal('Task 2'); 28 | expect(await todos[2].getVisibleText()).to.equal('Task 3'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /angular-example/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /angular-example/tests/intern-angular-shim.ts: -------------------------------------------------------------------------------- 1 | interface AngularShimConfig { 2 | appConfig?: string | SystemJSLoader.Config; 3 | testConfig?: string | SystemJSLoader.Config; 4 | require?: string | string[]; 5 | } 6 | 7 | intern.registerPlugin('angular-shim', async (config: AngularShimConfig) => { 8 | async function configureLoader(config: string | SystemJSLoader.Config) { 9 | if (config) { 10 | if (typeof config === "string") { 11 | await intern.loadScript(config); 12 | } else { 13 | SystemJS.config(config); 14 | } 15 | } 16 | } 17 | 18 | async function initTestEnv() { 19 | const { TestBed } = await SystemJS.import('@angular/core/testing'); 20 | const { BrowserDynamicTestingModule, platformBrowserDynamicTesting } = await SystemJS.import('@angular/platform-browser-dynamic/testing'); 21 | 22 | TestBed.initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting() 25 | ); 26 | 27 | intern.on('suiteAdd', suite => { 28 | suite['afterEach'] = () => { 29 | TestBed.resetTestingModule(); 30 | }; 31 | }); 32 | } 33 | 34 | await configureLoader(config.appConfig); 35 | await configureLoader(config.testConfig); 36 | 37 | if (config.require && config.require.length) { 38 | await intern.loadScript(config.require); 39 | } 40 | 41 | await initTestEnv(); 42 | }); 43 | -------------------------------------------------------------------------------- /angular-example/tests/services/store.ts: -------------------------------------------------------------------------------- 1 | const { describe, it, beforeEach } = intern.getInterface('bdd'); 2 | const { expect } = intern.getPlugin('chai'); 3 | 4 | import { TodoService, TodoObject, Todo } from 'app/services/store'; 5 | import { LocalStorageService } from 'app/services/localStorage'; 6 | 7 | import 'rxjs/add/operator/toPromise'; 8 | 9 | let objects: Todo[]; 10 | class MockLocalStorageService { 11 | get() { 12 | return objects; 13 | } 14 | 15 | set(value: any[]) { 16 | objects = value; 17 | } 18 | 19 | clear() { 20 | objects = []; 21 | } 22 | } 23 | 24 | describe('TodoStore', () => { 25 | let service: TodoService; 26 | let localStorage: MockLocalStorageService; 27 | 28 | beforeEach(() => { 29 | objects = [ 30 | new Todo({ id: 1, title: 'one' }), 31 | new Todo({ id: 2, title: 'two' }), 32 | new Todo({ id: 3, title: 'three' }) 33 | ]; 34 | localStorage = new MockLocalStorageService(); 35 | service = new TodoService(localStorage as any); 36 | }); 37 | 38 | it('should create a service', () => { 39 | expect(service).not.to.be.null; 40 | }); 41 | 42 | it('should return an Observable of Todos', async () => { 43 | const result = await service.get().toPromise(); 44 | expect(result).to.deep.equal(objects); 45 | expect(result).not.to.equal(objects); 46 | }); 47 | 48 | it('should add a new Todo', async () => { 49 | const result = await service.add('four').toPromise(); 50 | expect(result).to.have.lengthOf(4); 51 | expect(result[3].title).to.equal('four'); 52 | }); 53 | 54 | it('should update a Todo', async () => { 55 | const todo = objects[1]; 56 | todo.title = 'two edited'; 57 | const result = await service.update(todo).toPromise(); 58 | expect(result).to.have.lengthOf(3); 59 | expect(result[1]).to.deep.equal(todo); 60 | expect(result[1]).not.to.equal(todo); 61 | }); 62 | 63 | it('should set new Todos', async () => { 64 | const todos = [ 65 | new Todo({ title: 'foo' }), 66 | new Todo({ title: 'bar' }) 67 | ]; 68 | const result = await service.set(todos).toPromise(); 69 | expect(result).to.have.lengthOf(2); 70 | expect(result).to.be.deep.equal(todos); 71 | expect(result).not.to.equal(todos); 72 | }); 73 | 74 | it('should delete a Todo', async () => { 75 | const original = [...objects]; 76 | const result = await service.delete(objects[1]).toPromise(); 77 | expect(result).to.have.lengthOf(2); 78 | expect(result[0]).to.deep.equal(original[0]); 79 | expect(result[1]).to.deep.equal(original[2]); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /angular-example/tests/systemjs-istanbuljs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module hooks the SystemJS translate function to apply code coverage instrumentation. 3 | */ 4 | 5 | import { createInstrumenter } from 'istanbul-lib-instrument'; 6 | import { readFileSync } from 'fs'; 7 | import { parse } from 'url'; 8 | import { dirname, normalize, join } from 'path'; 9 | 10 | const istanbulGlobal = '__coverage__'; 11 | const sourceMapRegex = /(?:\/{2}[#@]{1,2}|\/\*)\s+sourceMappingURL\s*=\s*(data:(?:[^;]+;)+base64,)?(\S+)/; 12 | 13 | const instrumenter = createInstrumenter({ 14 | coverageVariable: istanbulGlobal 15 | }); 16 | 17 | const esInstrumenter = createInstrumenter({ 18 | coverageVariable: istanbulGlobal, 19 | esModules: true 20 | }); 21 | 22 | export function translate(this: SystemJSLoader.System, load: any) { 23 | const source = load.source; 24 | const meta = load.metadata; 25 | 26 | if ( 27 | meta.format === 'json' || 28 | meta.format === 'defined' || 29 | meta.loaderModule && meta.loaderModule.build === false 30 | ) { 31 | return source; 32 | } 33 | 34 | const match = sourceMapRegex.exec(load.source); 35 | let sourceMap: any; 36 | if (match) { 37 | const sourcePath = parse(load.address).pathname!; 38 | const mapPath = match[2]; 39 | const mapFile = normalize(join(dirname(sourcePath), mapPath)); 40 | try { 41 | sourceMap = JSON.parse(readFileSync(mapFile, { encoding: 'utf8' })); 42 | } 43 | catch (_error) { 44 | console.warn('Unable to load source map at ' + mapFile); 45 | } 46 | } 47 | 48 | const baseURL = (meta.istanbul && this.resolveSync(meta.istanbul.baseURL)) || this.baseURL!; 49 | const name = normalize(load.address.slice(baseURL.length)); 50 | 51 | try { 52 | if (meta.format === 'esm') { 53 | return esInstrumenter.instrumentSync(source, name, sourceMap); 54 | } 55 | else { 56 | return instrumenter.instrumentSync(source, name, sourceMap); 57 | } 58 | } 59 | catch (error) { 60 | const message = `Unable to add coverage instrumentation to "${name}".\n\t`; 61 | const newErr: SystemJSError = new Error(`${message}${error.message}`); 62 | newErr.stack = `${message}${error.stack}`; 63 | newErr.originalErr = error.originalErr || error; 64 | throw newErr; 65 | } 66 | } 67 | 68 | interface SystemJSError extends Error { 69 | originalErr?: Error; 70 | } 71 | -------------------------------------------------------------------------------- /angular-example/tests/systemjs.config.ts: -------------------------------------------------------------------------------- 1 | SystemJS.config({ 2 | map: { 3 | 'sinon': 'node_modules/sinon/pkg/sinon.js' 4 | }, 5 | packages: { 6 | '@angular/animations/browser/testing': { 7 | main: '../../bundles/animations-browser-testing.umd.js' 8 | }, 9 | '@angular/core/testing': { 10 | main: '../bundles/core-testing.umd.js' 11 | }, 12 | '@angular/common/testing': { 13 | main: '../bundles/common-testing.umd.js' 14 | }, 15 | '@angular/common/http/testing': { 16 | main: '../../bundles/common-http-testing.umd.js' 17 | }, 18 | '@angular/compiler/testing': { 19 | main: '../bundles/compiler-testing.umd.js' 20 | }, 21 | '@angular/platform-browser/testing': { 22 | main: '../bundles/platform-browser-testing.umd.js' 23 | }, 24 | '@angular/platform-browser-dynamic/testing': { 25 | main: '../bundles/platform-browser-dynamic-testing.umd.js' 26 | }, 27 | '@angular/router/testing': { 28 | main: '../bundles/router-testing.umd.js' 29 | }, 30 | '@angular/forms/testing': { 31 | main: '../bundles/forms-testing.umd.js' 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /angular-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "paths": { 10 | "app/*": [ 11 | "./app/*" 12 | ] 13 | }, 14 | "importHelpers": true, 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "removeComments": false, 18 | "noImplicitAny": true, 19 | "lib": [ 20 | "es5", 21 | "es2015.core", 22 | "es2015.symbol.wellknown", 23 | "es2015.collection", 24 | "dom" 25 | ], 26 | "types": [ 27 | "intern", 28 | "systemjs" 29 | ], 30 | "typeRoots": [ 31 | "./node_modules/@types", 32 | "./node_modules/intern/types" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /angularjs-example/README.md: -------------------------------------------------------------------------------- 1 | angular-1x-example 2 | ============= 3 | 4 | ## Setup 5 | 6 | 1. Install the JRE or JDK 7 | This demo runs with local Selenium, which Intern will automatically install. 8 | 9 | 2. Install intern command line interface 10 | 11 | ``` 12 | npm install -g intern-cli 13 | ``` 14 | 15 | 3. Install node modules and intern 16 | 17 | ``` 18 | npm install 19 | ``` 20 | 21 | ## Running tests 22 | 23 | * **Local browser tests** 24 | 25 | ``` 26 | intern serve 27 | ``` 28 | 29 | Navigate to `http://localhost:9000/node_modules/intern/client.html?config=tests/intern.js`. 30 | 31 | * **Remote node / browser tests** 32 | 33 | ``` 34 | intern run --webdriver 35 | ``` 36 | -------------------------------------------------------------------------------- /angularjs-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AngularJS • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 18 |
19 | 20 | 21 |
    22 |
  • 23 |
    24 | 25 | 26 | 27 |
    28 |
    29 | 30 |
    31 |
  • 32 |
33 |
34 |
35 | {{remainingCount}} 36 | 37 | 38 | 49 | 50 |
51 |
52 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /angularjs-example/js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', []); 11 | -------------------------------------------------------------------------------- /angularjs-example/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $location, todoStorage, filterFilter) { 10 | var todos = $scope.todos = todoStorage.get(); 11 | 12 | $scope.newTodo = ''; 13 | $scope.editedTodo = null; 14 | 15 | $scope.$watch('todos', function (newValue, oldValue) { 16 | $scope.remainingCount = filterFilter(todos, { completed: false }).length; 17 | $scope.completedCount = todos.length - $scope.remainingCount; 18 | $scope.allChecked = !$scope.remainingCount; 19 | if (newValue !== oldValue) { // This prevents unneeded calls to the local storage 20 | todoStorage.put(todos); 21 | } 22 | }, true); 23 | 24 | if ($location.path() === '') { 25 | $location.path('/'); 26 | } 27 | 28 | $scope.location = $location; 29 | 30 | $scope.$watch('location.path()', function (path) { 31 | $scope.statusFilter = (path === '/active') ? 32 | { completed: false } : (path === '/completed') ? 33 | { completed: true } : null; 34 | }); 35 | 36 | $scope.addTodo = function () { 37 | var newTodo = $scope.newTodo.trim(); 38 | if (!newTodo.length) { 39 | return; 40 | } 41 | 42 | todos.push({ 43 | title: newTodo, 44 | completed: false 45 | }); 46 | 47 | $scope.newTodo = ''; 48 | }; 49 | 50 | $scope.editTodo = function (todo) { 51 | $scope.editedTodo = todo; 52 | // Clone the original todo to restore it on demand. 53 | $scope.originalTodo = angular.extend({}, todo); 54 | }; 55 | 56 | $scope.doneEditing = function (todo) { 57 | $scope.editedTodo = null; 58 | todo.title = todo.title.trim(); 59 | 60 | if (!todo.title) { 61 | $scope.removeTodo(todo); 62 | } 63 | }; 64 | 65 | $scope.revertEditing = function (todo) { 66 | todos[todos.indexOf(todo)] = $scope.originalTodo; 67 | $scope.doneEditing($scope.originalTodo); 68 | }; 69 | 70 | $scope.removeTodo = function (todo) { 71 | todos.splice(todos.indexOf(todo), 1); 72 | }; 73 | 74 | $scope.clearCompletedTodos = function () { 75 | $scope.todos = todos = todos.filter(function (val) { 76 | return !val.completed; 77 | }); 78 | }; 79 | 80 | $scope.markAll = function (completed) { 81 | todos.forEach(function (todo) { 82 | todo.completed = completed; 83 | }); 84 | }; 85 | }); -------------------------------------------------------------------------------- /angularjs-example/js/directives/todoBlur.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to loses focus 6 | */ 7 | todomvc.directive('todoBlur', function () { 8 | return function (scope, elem, attrs) { 9 | elem.bind('blur', function () { 10 | scope.$apply(attrs.todoBlur); 11 | }); 12 | }; 13 | }); 14 | -------------------------------------------------------------------------------- /angularjs-example/js/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to gets 6 | * an `escape` keydown event. 7 | */ 8 | todomvc.directive('todoEscape', function () { 9 | var ESCAPE_KEY = 27; 10 | return function (scope, elem, attrs) { 11 | elem.bind('keydown', function (event) { 12 | if (event.keyCode === ESCAPE_KEY) { 13 | scope.$apply(attrs.todoEscape); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /angularjs-example/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function todoFocus($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /angularjs-example/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function () { 8 | var STORAGE_ID = 'todos-angularjs'; 9 | 10 | return { 11 | get: function () { 12 | return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]'); 13 | }, 14 | 15 | put: function (todos) { 16 | localStorage.setItem(STORAGE_ID, JSON.stringify(todos)); 17 | } 18 | }; 19 | }); -------------------------------------------------------------------------------- /angularjs-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-intern-example", 3 | "version": "0.2.0", 4 | "description": "An example showing a typical Angular 1.x application tested with Intern", 5 | "devDependencies": { 6 | "angular-mocks": "~1.2.18", 7 | "intern": "~3.4.2" 8 | }, 9 | "dependencies": { 10 | "angular": "~1.8.0", 11 | "todomvc-common": "~0.1.4" 12 | }, 13 | "scripts": { 14 | "test": "intern-runner config=tests/intern" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /angularjs-example/tests/all.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './directives/todoBlurFocus', 3 | './controllers/todoCtrl' 4 | ], function () {}); -------------------------------------------------------------------------------- /angularjs-example/tests/directives/todoBlurFocus.js: -------------------------------------------------------------------------------- 1 | /*global angular*/ 2 | 3 | define([ 4 | 'intern/chai!expect', 5 | 'intern!bdd', 6 | 'intern/order!angular/angular', 7 | 'intern/order!angular-mocks/angular-mocks', 8 | 'intern/order!todo/app', 9 | 'intern/order!todo/directives/todoBlur', 10 | 'intern/order!todo/directives/todoFocus' 11 | ], function (expect, bdd) { 12 | 13 | function inject (fn) { 14 | return function() { 15 | angular.injector(['ng', 'ngMock', 'todomvc']).invoke(fn); 16 | } 17 | } 18 | 19 | bdd.describe('todoBlur directive', function () { 20 | var scope, compile, browser; 21 | 22 | bdd.beforeEach(inject(function ($rootScope, $compile, $browser) { 23 | scope = $rootScope.$new(); 24 | scope.mock = { 25 | called: false, 26 | call: function () { this.called = true; } 27 | }; 28 | compile = $compile; 29 | browser = $browser; 30 | })); 31 | 32 | bdd.it('should $apply on blur', function () { 33 | var el = angular.element(''); 34 | compile(el)(scope); 35 | 36 | el.triggerHandler('blur'); 37 | scope.$digest(); 38 | 39 | expect(scope.mock.called).to.be.true; 40 | }); 41 | 42 | bdd.it('should focus on truthy expression', function () { 43 | scope.focus = false; 44 | 45 | var el = angular.element(''); 46 | compile(el)(scope); 47 | 48 | expect(browser.deferredFns).to.have.length(0); 49 | 50 | scope.$apply(function () { 51 | scope.focus = true; 52 | }); 53 | 54 | expect(browser.deferredFns).to.have.length(1); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /angularjs-example/tests/functional/Todo.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!object', 3 | 'intern/chai!assert', 4 | 'require' 5 | ], function (registerSuite, assert, require) { 6 | var url = '../../index.html'; 7 | 8 | registerSuite({ 9 | name: 'Todo (functional)', 10 | 11 | 'submit form': function () { 12 | return this.remote 13 | .get(require.toUrl(url)) 14 | .findById('new-todo') 15 | .click() 16 | .pressKeys('Task 1') 17 | .pressKeys('\n') 18 | .pressKeys('Task 2') 19 | .pressKeys('\n') 20 | .pressKeys('Task 3') 21 | .getProperty('value') 22 | .then(function (val) { 23 | assert.ok(val.indexOf('Task 3') > -1, 'Task 3 should remain in the new todo'); 24 | }); 25 | } 26 | }); 27 | }); -------------------------------------------------------------------------------- /angularjs-example/tests/intern.js: -------------------------------------------------------------------------------- 1 | // Learn more about configuring this file at . 2 | // These default settings work OK for most people. The options that *must* be changed below are the 3 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites. 4 | define({ 5 | // Default desired capabilities for all environments. Individual capabilities can be overridden by any of the 6 | // specified browser environments in the `environments` array below as well. See 7 | // for links to the different capabilities options for 8 | // different services. 9 | // 10 | // Note that the `build` capability will be filled in with the current commit ID or build tag from the CI 11 | // environment automatically 12 | capabilities: {}, 13 | 14 | // Browsers to run integration testing against. Options that will be permutated are browserName, version, platform, 15 | // and platformVersion; any other capabilities options specified for an environment will be copied as-is. Note that 16 | // browser and platform names, and version number formats, may differ between cloud testing systems. 17 | environments: [ { browserName: 'chrome' } ], 18 | 19 | // Maximum number of simultaneous integration tests that should be executed on the remote WebDriver service 20 | maxConcurrency: 3, 21 | 22 | // Name of the tunnel class to use for WebDriver tests. 23 | // See for built-in options 24 | tunnel: 'SeleniumTunnel', 25 | 26 | // Configuration options for the module loader; any AMD configuration options supported by the AMD loader in use 27 | // can be used here. 28 | // If you want to use a different loader than the default loader, see 29 | // for more information. 30 | loaderOptions: { 31 | // Packages that should be registered with the loader in each testing environment 32 | packages: [ 33 | { name: 'todo', location: 'js' }, 34 | { name: 'angular', location: 'node_modules/angular' }, 35 | { name: 'angular-mocks', location: 'node_modules/angular-mocks' } 36 | ] 37 | }, 38 | 39 | // Unit test suite(s) to run in each browser 40 | suites: [ 'tests/all' ], 41 | 42 | // Functional test suite(s) to execute against each browser once unit tests are completed 43 | functionalSuites: [ 'tests/functional/Todo' ], 44 | 45 | // A regular expression matching URLs to files that should not be included in code coverage analysis. Set to `true` 46 | // to completely disable code coverage. 47 | excludeInstrumentation: /^(?:tests|node_modules)\// 48 | }); 49 | -------------------------------------------------------------------------------- /backbone-example/README.md: -------------------------------------------------------------------------------- 1 | backbone-example 2 | ============= 3 | 4 | ## Setup 5 | 6 | 1. Install the JRE or JDK 7 | This demo runs with local Selenium, which Intern will automatically install. 8 | 9 | 2. Install intern command line interface 10 | 11 | ``` 12 | npm install -g intern-cli 13 | ``` 14 | 15 | 3. Install node modules and intern 16 | 17 | ``` 18 | npm install 19 | ``` 20 | 21 | ## Running tests 22 | 23 | * **Local browser tests** 24 | 25 | ``` 26 | intern serve 27 | ``` 28 | 29 | Navigate to `http://localhost:9000/node_modules/intern/client.html?config=tests/intern.js`. 30 | 31 | * **Remote node / browser tests** 32 | 33 | ``` 34 | intern run --webdriver 35 | ``` 36 | -------------------------------------------------------------------------------- /backbone-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Backbone.js • TodoMVC 7 | 8 | 9 | 10 |
11 | 15 |
16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 27 | 35 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /backbone-example/js/app.js: -------------------------------------------------------------------------------- 1 | /*global $ */ 2 | /*jshint unused:false */ 3 | var app = app || {}; 4 | var ENTER_KEY = 13; 5 | 6 | $(function () { 7 | 'use strict'; 8 | 9 | // kick things off by creating the `App` 10 | new app.AppView(); 11 | }); 12 | -------------------------------------------------------------------------------- /backbone-example/js/collections/todos.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Collection 8 | // --------------- 9 | 10 | // The collection of todos is backed by *localStorage* instead of a remote 11 | // server. 12 | var Todos = Backbone.Collection.extend({ 13 | // Reference to this collection's model. 14 | model: app.Todo, 15 | 16 | // Save all of the todo items under the `"todos"` namespace. 17 | localStorage: new Backbone.LocalStorage('todos-backbone'), 18 | 19 | // Filter down the list of all todo items that are finished. 20 | completed: function () { 21 | return this.filter(function (todo) { 22 | return todo.get('completed'); 23 | }); 24 | }, 25 | 26 | // Filter down the list to only todo items that are still not finished. 27 | remaining: function () { 28 | return this.without.apply(this, this.completed()); 29 | }, 30 | 31 | // We keep the Todos in sequential order, despite being saved by unordered 32 | // GUID in the database. This generates the next order number for new items. 33 | nextOrder: function () { 34 | if (!this.length) { 35 | return 1; 36 | } 37 | return this.last().get('order') + 1; 38 | }, 39 | 40 | // Todos are sorted by their original insertion order. 41 | comparator: function (todo) { 42 | return todo.get('order'); 43 | } 44 | }); 45 | 46 | // Create our global collection of **Todos**. 47 | app.todos = new Todos(); 48 | })(); 49 | -------------------------------------------------------------------------------- /backbone-example/js/models/todo.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Model 8 | // ---------- 9 | 10 | // Our basic **Todo** model has `title`, `order`, and `completed` attributes. 11 | app.Todo = Backbone.Model.extend({ 12 | // Default attributes for the todo 13 | // and ensure that each todo created has `title` and `completed` keys. 14 | defaults: { 15 | title: '', 16 | completed: false 17 | }, 18 | 19 | // Toggle the `completed` state of this todo item. 20 | toggle: function () { 21 | this.save({ 22 | completed: !this.get('completed') 23 | }); 24 | } 25 | }); 26 | })(); 27 | -------------------------------------------------------------------------------- /backbone-example/js/routers/router.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Router 8 | // ---------- 9 | var TodoRouter = Backbone.Router.extend({ 10 | routes: { 11 | '*filter': 'setFilter' 12 | }, 13 | 14 | setFilter: function (param) { 15 | // Set the current filter to be used 16 | app.TodoFilter = param || ''; 17 | 18 | // Trigger a collection filter event, causing hiding/unhiding 19 | // of Todo view items 20 | app.todos.trigger('filter'); 21 | } 22 | }); 23 | 24 | app.TodoRouter = new TodoRouter(); 25 | Backbone.history.start(); 26 | })(); 27 | -------------------------------------------------------------------------------- /backbone-example/js/views/app-view.js: -------------------------------------------------------------------------------- 1 | /*global Backbone, jQuery, _, ENTER_KEY */ 2 | var app = app || {}; 3 | 4 | (function ($) { 5 | 'use strict'; 6 | 7 | // The Application 8 | // --------------- 9 | 10 | // Our overall **AppView** is the top-level piece of UI. 11 | app.AppView = Backbone.View.extend({ 12 | 13 | // Instead of generating a new element, bind to the existing skeleton of 14 | // the App already present in the HTML. 15 | el: '#todoapp', 16 | 17 | // Our template for the line of statistics at the bottom of the app. 18 | statsTemplate: _.template($('#stats-template').html()), 19 | 20 | // Delegated events for creating new items, and clearing completed ones. 21 | events: { 22 | 'keypress #new-todo': 'createOnEnter', 23 | 'click #clear-completed': 'clearCompleted', 24 | 'click #toggle-all': 'toggleAllComplete' 25 | }, 26 | 27 | // At initialization we bind to the relevant events on the `Todos` 28 | // collection, when items are added or changed. Kick things off by 29 | // loading any preexisting todos that might be saved in *localStorage*. 30 | initialize: function () { 31 | this.allCheckbox = this.$('#toggle-all')[0]; 32 | this.$input = this.$('#new-todo'); 33 | this.$footer = this.$('#footer'); 34 | this.$main = this.$('#main'); 35 | 36 | this.listenTo(app.todos, 'add', this.addOne); 37 | this.listenTo(app.todos, 'reset', this.addAll); 38 | this.listenTo(app.todos, 'change:completed', this.filterOne); 39 | this.listenTo(app.todos, 'filter', this.filterAll); 40 | this.listenTo(app.todos, 'all', this.render); 41 | 42 | app.todos.fetch(); 43 | }, 44 | 45 | // Re-rendering the App just means refreshing the statistics -- the rest 46 | // of the app doesn't change. 47 | render: function () { 48 | var completed = app.todos.completed().length; 49 | var remaining = app.todos.remaining().length; 50 | 51 | if (app.todos.length) { 52 | this.$main.show(); 53 | this.$footer.show(); 54 | 55 | this.$footer.html(this.statsTemplate({ 56 | completed: completed, 57 | remaining: remaining 58 | })); 59 | 60 | this.$('#filters li a') 61 | .removeClass('selected') 62 | .filter('[href="#/' + (app.TodoFilter || '') + '"]') 63 | .addClass('selected'); 64 | } else { 65 | this.$main.hide(); 66 | this.$footer.hide(); 67 | } 68 | 69 | this.allCheckbox.checked = !remaining; 70 | }, 71 | 72 | // Add a single todo item to the list by creating a view for it, and 73 | // appending its element to the `
      `. 74 | addOne: function (todo) { 75 | var view = new app.TodoView({ model: todo }); 76 | $('#todo-list').append(view.render().el); 77 | }, 78 | 79 | // Add all items in the **Todos** collection at once. 80 | addAll: function () { 81 | this.$('#todo-list').html(''); 82 | app.todos.each(this.addOne, this); 83 | }, 84 | 85 | filterOne: function (todo) { 86 | todo.trigger('visible'); 87 | }, 88 | 89 | filterAll: function () { 90 | app.todos.each(this.filterOne, this); 91 | }, 92 | 93 | // Generate the attributes for a new Todo item. 94 | newAttributes: function () { 95 | return { 96 | title: this.$input.val().trim(), 97 | order: app.todos.nextOrder(), 98 | completed: false 99 | }; 100 | }, 101 | 102 | // If you hit return in the main input field, create new **Todo** model, 103 | // persisting it to *localStorage*. 104 | createOnEnter: function (e) { 105 | if (e.which !== ENTER_KEY || !this.$input.val().trim()) { 106 | return; 107 | } 108 | 109 | app.todos.create(this.newAttributes()); 110 | this.$input.val(''); 111 | }, 112 | 113 | // Clear all completed todo items, destroying their models. 114 | clearCompleted: function () { 115 | _.invoke(app.todos.completed(), 'destroy'); 116 | return false; 117 | }, 118 | 119 | toggleAllComplete: function () { 120 | var completed = this.allCheckbox.checked; 121 | 122 | app.todos.each(function (todo) { 123 | todo.save({ 124 | 'completed': completed 125 | }); 126 | }); 127 | } 128 | }); 129 | })(jQuery); 130 | -------------------------------------------------------------------------------- /backbone-example/js/views/todo-view.js: -------------------------------------------------------------------------------- 1 | /*global Backbone, jQuery, _, ENTER_KEY */ 2 | var app = app || {}; 3 | 4 | (function ($) { 5 | 'use strict'; 6 | 7 | // Todo Item View 8 | // -------------- 9 | 10 | // The DOM element for a todo item... 11 | app.TodoView = Backbone.View.extend({ 12 | //... is a list tag. 13 | tagName: 'li', 14 | 15 | // Cache the template function for a single item. 16 | template: _.template($('#item-template').html()), 17 | 18 | // The DOM events specific to an item. 19 | events: { 20 | 'click .toggle': 'toggleCompleted', 21 | 'dblclick label': 'edit', 22 | 'click .destroy': 'clear', 23 | 'keypress .edit': 'updateOnEnter', 24 | 'blur .edit': 'close' 25 | }, 26 | 27 | // The TodoView listens for changes to its model, re-rendering. Since there's 28 | // a one-to-one correspondence between a **Todo** and a **TodoView** in this 29 | // app, we set a direct reference on the model for convenience. 30 | initialize: function () { 31 | this.listenTo(this.model, 'change', this.render); 32 | this.listenTo(this.model, 'destroy', this.remove); 33 | this.listenTo(this.model, 'visible', this.toggleVisible); 34 | }, 35 | 36 | // Re-render the titles of the todo item. 37 | render: function () { 38 | this.$el.html(this.template(this.model.toJSON())); 39 | this.$el.toggleClass('completed', this.model.get('completed')); 40 | this.toggleVisible(); 41 | this.$input = this.$('.edit'); 42 | return this; 43 | }, 44 | 45 | toggleVisible: function () { 46 | this.$el.toggleClass('hidden', this.isHidden()); 47 | }, 48 | 49 | isHidden: function () { 50 | var isCompleted = this.model.get('completed'); 51 | return (// hidden cases only 52 | (!isCompleted && app.TodoFilter === 'completed') || 53 | (isCompleted && app.TodoFilter === 'active') 54 | ); 55 | }, 56 | 57 | // Toggle the `"completed"` state of the model. 58 | toggleCompleted: function () { 59 | this.model.toggle(); 60 | }, 61 | 62 | // Switch this view into `"editing"` mode, displaying the input field. 63 | edit: function () { 64 | this.$el.addClass('editing'); 65 | this.$input.focus(); 66 | }, 67 | 68 | // Close the `"editing"` mode, saving changes to the todo. 69 | close: function () { 70 | var trimmedValue = this.$input.val().trim(); 71 | this.$input.val(trimmedValue); 72 | 73 | if (trimmedValue) { 74 | this.model.save({ title: trimmedValue }); 75 | } else { 76 | this.clear(); 77 | } 78 | 79 | this.$el.removeClass('editing'); 80 | }, 81 | 82 | // If you hit `enter`, we're through editing the item. 83 | updateOnEnter: function (e) { 84 | if (e.which === ENTER_KEY) { 85 | this.close(); 86 | } 87 | }, 88 | 89 | // Remove the item, destroy the model from *localStorage* and delete its view. 90 | clear: function () { 91 | this.model.destroy(); 92 | } 93 | }); 94 | })(jQuery); 95 | -------------------------------------------------------------------------------- /backbone-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone-intern-example", 3 | "version": "0.2.0", 4 | "description": "An example showing a typical Backbone application tested with Intern", 5 | "devDependencies": { 6 | "intern": "~3.4.2" 7 | }, 8 | "dependencies": { 9 | "backbone": "~1.0.0", 10 | "backbone.localstorage": "~1.1.0", 11 | "underscore": "~1.4.4", 12 | "jquery": "~3.5.1", 13 | "todomvc-common": "~0.1.4" 14 | }, 15 | "scripts": { 16 | "test": "intern-runner config=tests/intern" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backbone-example/tests/all.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './models/todo' 3 | ], function () {}); -------------------------------------------------------------------------------- /backbone-example/tests/functional/Todo.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!object', 3 | 'intern/chai!assert', 4 | 'require' 5 | ], function (registerSuite, assert, require) { 6 | var url = '../../index.html'; 7 | 8 | registerSuite({ 9 | name: 'Todo (functional)', 10 | 11 | 'submit form': function () { 12 | return this.remote 13 | .get(require.toUrl(url)) 14 | .findById('new-todo') 15 | .click() 16 | .pressKeys('Task 1') 17 | .pressKeys('\n') 18 | .pressKeys('Task 2') 19 | .pressKeys('\n') 20 | .pressKeys('Task 3') 21 | .getProperty('value') 22 | .then(function (val) { 23 | assert.ok(val.indexOf('Task 3') > -1, 'Task 3 should remain in the new todo'); 24 | }); 25 | } 26 | }); 27 | }); -------------------------------------------------------------------------------- /backbone-example/tests/intern.js: -------------------------------------------------------------------------------- 1 | // Learn more about configuring this file at . 2 | // These default settings work OK for most people. The options that *must* be changed below are the 3 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites. 4 | define([ 'intern' ], function (intern) { 5 | var config = { 6 | // Default desired capabilities for all environments. Individual capabilities can be overridden by any of the 7 | // specified browser environments in the `environments` array below as well. See 8 | // for links to the different capabilities options for 9 | // different services. 10 | // 11 | // Note that the `build` capability will be filled in with the current commit ID or build tag from the CI 12 | // environment automatically 13 | capabilities: {}, 14 | 15 | // Browsers to run integration testing against. Options that will be permutated are browserName, version, 16 | // platform, and platformVersion; any other capabilities options specified for an environment will be copied 17 | // as-is. Note that browser and platform names, and version number formats, may differ between cloud testing 18 | // systems. 19 | environments: [ { browserName: 'chrome' } ], 20 | 21 | // Maximum number of simultaneous integration tests that should be executed on the remote WebDriver service 22 | maxConcurrency: 3, 23 | 24 | // Name of the tunnel class to use for WebDriver tests. 25 | // See for built-in options 26 | tunnel: 'SeleniumTunnel', 27 | 28 | // Configuration options for the module loader; any AMD configuration options supported by the AMD loader in use 29 | // can be used here. 30 | // If you want to use a different loader than the default loader, see 31 | // for more information. 32 | loaderOptions: { 33 | // Packages that should be registered with the loader in each testing environment 34 | packages: [ 35 | { name: 'todo', location: 'js' }, 36 | { name: 'jquery', location: 'node_modules/jquery' }, 37 | { name: 'underscore', location: 'node_modules/underscore' }, 38 | { name: 'backbone', location: 'node_modules/backbone' }, 39 | { name: 'backboneStorage', location: 'node_modules/backbone.localstorage' } 40 | ] 41 | }, 42 | 43 | // Unit test suite(s) to run in each browser 44 | suites: [ 'tests/all' ], 45 | 46 | // Functional test suite(s) to execute against each browser once unit tests are completed 47 | functionalSuites: [ 'tests/functional/Todo' ], 48 | 49 | // A regular expression matching URLs to files that should not be included in code coverage analysis. Set to 50 | // `true` to completely disable code coverage. 51 | excludeInstrumentation: /^(?:tests|node_modules)\// 52 | }; 53 | 54 | if (intern.mode === 'runner') { 55 | config.reporters = [ 'Runner', 'Lcovhtml' ]; 56 | } 57 | 58 | return config; 59 | }); 60 | -------------------------------------------------------------------------------- /backbone-example/tests/models/todo.js: -------------------------------------------------------------------------------- 1 | /*global $:false, app:false */ 2 | 3 | define([ 4 | 'intern!object', 5 | 'intern/chai!assert', 6 | 'intern/order!jquery/jquery', 7 | 'intern/order!underscore/underscore', 8 | 'intern/order!backbone/backbone', 9 | 'intern/order!todo/models/todo' 10 | ], function (registerSuite, assert) { 11 | var todo, 12 | ajax; 13 | 14 | registerSuite({ 15 | name: 'todo model', 16 | 17 | setup: function () { 18 | // Extend the Todo model only to add a urlRoot. This 19 | // property is required for a model to call save() 20 | // without throwing an error 21 | var Model = app.Todo.extend({ 22 | urlRoot: 'mockUrlRoot' 23 | }); 24 | todo = new Model(); 25 | 26 | // Mock the jquery ajax method for now 27 | ajax = $.ajax; 28 | $.ajax = function () {}; 29 | }, 30 | 31 | teardown: function () { 32 | $.ajax = ajax; 33 | }, 34 | 35 | defaults: function () { 36 | assert.isFalse(todo.get('completed'), 'A Todo model should default the completed property to false'); 37 | assert.strictEqual(todo.get('title'), '', 'A Todo model should default the title property to an empty string'); 38 | }, 39 | 40 | toggle: function () { 41 | todo.toggle(); 42 | assert.isTrue(todo.get('completed'), '', 'Completed property should switch to true after being toggled for the first time'); 43 | todo.toggle(); 44 | assert.isFalse(todo.get('completed'), '', 'Completed property should switch back to false after being toggled again'); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /dojo-example/README.md: -------------------------------------------------------------------------------- 1 | dojo-example 2 | ============= 3 | 4 | It is based on the [TodoMVC Dojo Example](http://todomvc.com/examples/dojo/). 5 | 6 | ## Setup 7 | 8 | 1. Install the JRE or JDK. This demo uses Selenium, which requires Java, to run WebDriver tests. 9 | 10 | 2. Install node modules 11 | ``` 12 | $ npm install 13 | ``` 14 | 15 | ## Running tests 16 | 17 | **Run unit and functional tests in Chrome** 18 | 19 | $ npm test 20 | 21 | **Run unit and functional tests in other browsers** 22 | 23 | $ npm test config=@firefox 24 | $ npm test config=@ie 25 | 26 | Note that the above commands all require that the browser be available on the test system. 27 | -------------------------------------------------------------------------------- /dojo-example/css/app.css: -------------------------------------------------------------------------------- 1 | #clear-completed, #footer, #main, 2 | .plural { 3 | display: none; 4 | } 5 | 6 | #todoapp.todos_selected #clear-completed, 7 | #todoapp.todos_present #footer, 8 | #todoapp.todos_present #main, 9 | .multiple .plural { 10 | display: inherit; 11 | } 12 | 13 | #todo-list li.hidden { 14 | display: none; 15 | } 16 | 17 | #todo-list li .toggle.dijitChecked:after { 18 | color: #85ada7; 19 | text-shadow: 0 1px 0 #669991; 20 | bottom: 1px; 21 | position: relative; 22 | } 23 | 24 | /* When checkbox is selected, score through todo item content */ 25 | .dijitCheckBoxChecked + .todo-content { 26 | color: #666; 27 | text-decoration: line-through; 28 | } 29 | 30 | /* When checkbox is selected, score through todo item content after an edit */ 31 | .dijitCheckBoxChecked + .dijitInline + .todo-content { 32 | color: #666; 33 | text-decoration: line-through; 34 | } 35 | 36 | #todo-list .dijitCheckBoxInput { 37 | opacity: 0; 38 | position: absolute; 39 | top: 14px; 40 | z-index: 10; 41 | } 42 | 43 | /** Match up inline edit box with styling */ 44 | .dijitInputInner { 45 | position: relative; 46 | margin: 0; 47 | width: 100%; 48 | font-size: 24px; 49 | font-family: inherit; 50 | line-height: 1.4em; 51 | border: 0; 52 | outline: none; 53 | color: inherit; 54 | padding: 6px; 55 | border: 1px solid #999; 56 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 57 | -webkit-box-sizing: border-box; 58 | -moz-box-sizing: border-box; 59 | -ms-box-sizing: border-box; 60 | -o-box-sizing: border-box; 61 | box-sizing: border-box; 62 | -webkit-font-smoothing: antialiased; 63 | -moz-font-smoothing: antialiased; 64 | -ms-font-smoothing: antialiased; 65 | -o-font-smoothing: antialiased; 66 | font-smoothing: antialiased; 67 | } 68 | 69 | #todo-list li .dijitInputInner { 70 | display: block; 71 | width: 506px; 72 | padding: 13px 17px 12px 17px; 73 | margin: 0 0 0 43px; 74 | z-index: 10; 75 | } 76 | 77 | /** Ugh, force override of edit container margin */ 78 | #todo-list li span.dijitInline { 79 | margin: 0 !important; 80 | } 81 | 82 | /** 83 | * Inline edit box doesn't provide indication via class names 84 | * when a box is 'live'. Style values are set manually. Use the 85 | * opacity change as indicator... :( */ 86 | .inline_edit[style~='0;'] ~ .toggle { 87 | visibility: hidden !important; 88 | } 89 | 90 | .dijitOffScreen { /* For 1.8 in-line edit box */ 91 | position: absolute !important; 92 | left: 50% !important; 93 | top: -10000px !important; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /dojo-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dojo • TodoMVC 7 | 8 | 9 | 10 | 11 |
      12 | 17 | 19 | 20 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /dojo-example/intern.json: -------------------------------------------------------------------------------- 1 | { 2 | "loader": { 3 | "script": "dojo", 4 | "options": { 5 | "packages": [ 6 | { "name": "todo", "location": "js/todo" }, 7 | { "name": "dojo", "location": "node_modules/dojo" }, 8 | { "name": "dojox", "location": "node_modules/dojox" }, 9 | { "name": "dijit", "location": "node_modules/dijit" } 10 | ] 11 | } 12 | }, 13 | "functionalSuites": "tests/functional/Todo", 14 | "filterErrorStack": false, 15 | "suites": "tests/all", 16 | "environments": [ "chrome", "node" ], 17 | "configs": { 18 | "ie": { 19 | "environments": { 20 | "browserName": "internet explorer", 21 | "requireWindowFocus": true 22 | }, 23 | "tunnelOptions": { 24 | "version": "3.4.0", 25 | "drivers": [{ "name": "ie", "version": "3.4.0" }] 26 | } 27 | }, 28 | "firefox": { 29 | "environments": "firefox", 30 | "tunnelOptions": { 31 | "drivers": [{ "name": "firefox" }] 32 | } 33 | }, 34 | "edge": { 35 | "environments": "MicrosoftEdge", 36 | "tunnelOptions": { 37 | "drivers": [{ "name": "edge" }] 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dojo-example/js/todo/CssToggleWidget.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/array", 3 | "dojo/_base/declare", 4 | "dojo/_base/lang", 5 | "dojo/dom-class", 6 | "dijit/_WidgetBase" 7 | ], function(array, declare, lang, domClass, _WidgetBase){ 8 | return declare(_WidgetBase, { 9 | // summary: 10 | // Widget supporting widget attributes with classExists type. 11 | // classExists type allows boolean value of an attribute to reflect existence of a CSS class in a DOM node in the widget. 12 | // example: 13 | // In this example, the text will be bold when the check box is checked. 14 | // | 15 | // | 16 | // | 20 | // | 25 | // | 26 | // | 27 | // | 28 | // | 29 | // |
      This text will be bold when above check box is checked.
      32 | // | 33 | // | 34 | 35 | _attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){ 36 | // summary: 37 | // Handle widget attribute with classExists type. 38 | // See dijit/_WidgetBase._attrToDom() for more details. 39 | 40 | var callee = arguments.callee; 41 | array.forEach((function(){ return lang.isArray(commands) ? commands.slice(0) : [commands]; })(arguments.length >= 3 ? commands : this.attributeMap[attr]), function(command){ 42 | command.type != "classExists" ? 43 | this.inherited("_attrToDom", lang.mixin([attr, value, command], {callee: callee})) : 44 | domClass.toggle(this[command.node || "domNode"], command.className || attr, value); 45 | }, this); 46 | } 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /dojo-example/js/todo/TodoList.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dijit/_TemplatedMixin", 4 | "dijit/_WidgetsInTemplateMixin", 5 | "dojox/mvc/WidgetList", 6 | "dojox/mvc/_InlineTemplateMixin", 7 | "todo/CssToggleWidget", 8 | "todo/ctrl/_HashCompletedMixin" 9 | ], function(declare, _TemplatedMixin, _WidgetsInTemplateMixin, WidgetList, _InlineTemplateMixin, CssToggleWidget, _HashCompletedMixin){ 10 | return declare([WidgetList, _InlineTemplateMixin], { 11 | childClz: declare([CssToggleWidget, _TemplatedMixin, _WidgetsInTemplateMixin, _HashCompletedMixin], { 12 | _setCompletedAttr: {type: "classExists", className: "completed"}, 13 | _setHiddenAttr: {type: "classExists", className: "hidden"}, 14 | 15 | onRemoveClick: function(){ 16 | this.parent.listCtrl.removeItem(this.uniqueId); 17 | }, 18 | 19 | onEditBoxChange: function(){ 20 | if(!this.editBox.value){ 21 | this.parent.listCtrl.removeItem(this.uniqueId); 22 | } 23 | } 24 | }) 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /dojo-example/js/todo/app.html: -------------------------------------------------------------------------------- 1 |
      2 | 7 | 8 |
      9 | 10 | 11 |
        12 |
      • 13 |
        14 | 16 | 17 | 18 | 20 |
        21 |
      • 22 |
      23 |
      24 | 25 |
      26 | 27 | 28 | 29 | 30 | items left 31 | 32 | 43 | 46 |
      47 |
      48 | -------------------------------------------------------------------------------- /dojo-example/js/todo/app18.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/lang", 4 | "dojo/_base/unload", 5 | "dojo/keys", 6 | "dojo/string", 7 | "dijit/_TemplatedMixin", 8 | "dijit/_WidgetsInTemplateMixin", 9 | "todo/CssToggleWidget", 10 | "dojo/text!./app18.html", 11 | // Below modules are referred in template. 12 | // dijit/_WidgetsInTemplateMixin requires all modules referred in template to have been loaded before it's instantiated. 13 | "dijit/form/CheckBox", 14 | "dijit/form/TextBox", 15 | "dojox/mvc/at", 16 | "todo/TodoList", 17 | "todo/ctrl/RouteController", 18 | "todo/ctrl/TodoListRefController", 19 | "todo/ctrl/TodoRefController", 20 | "todo/form/InlineEditBox", 21 | "todo/misc/HashSelectedConverter", 22 | "todo/misc/LessThanOrEqualToConverter", 23 | "todo/model/SimpleTodoModel", 24 | "todo/store/LocalStorage" 25 | ], function(declare, lang, unload, keys, string, _TemplatedMixin, _WidgetsInTemplateMixin, CssToggleWidget, template){ 26 | return declare([CssToggleWidget, _TemplatedMixin, _WidgetsInTemplateMixin], { 27 | // summary: 28 | // A widgets-in-template widget that composes the application UI of TodoMVC (Dojo 1.8 version). 29 | // Also, this class inherits todo/CssToggleWidget so that it can react to change in "present"/"complete" attributes and add/remove CSS class to the root DOM node of this widget. 30 | 31 | // templateString: String 32 | // The HTML of widget template. 33 | templateString: template, 34 | 35 | // _setPresentAttr: Object 36 | // A object used by todo/CssToggleWidget to reflect true/false state of "present" attribute to existence of "todos_present" CSS class in this widget's root DOM. 37 | _setPresentAttr: {type: "classExists", className: "todos_present"}, 38 | 39 | // _setCompleteAttr: Object 40 | // A object used by todo/CssToggleWidget to reflect true/false state of "complete" attribute to existence of "todos_selected" CSS class in this widget's root DOM. 41 | _setCompleteAttr: {type: "classExists", className: "todos_selected"}, 42 | 43 | startup: function(){ 44 | // summary: 45 | // A function called after the DOM fragment declaring this controller is added to the document. 46 | // See documentation for dijit/_WidgetBase.startup() for more details. 47 | 48 | var _self = this; 49 | this.inherited(arguments); 50 | unload.addOnUnload(function(){ 51 | _self.destroy(); // When this page is being unloaded, call destroy callbacks of inner-widgets to let them clean up 52 | }); 53 | }, 54 | 55 | onKeyPressNewItem: function(/*DOMEvent*/ event){ 56 | // summary: 57 | // Handle key press events for the input field for new todo. 58 | // description: 59 | // If user has pressed enter, add current text value as new todo item in the model. 60 | 61 | if(event.keyCode !== keys.ENTER || !string.trim(event.target.value).length){ 62 | return; // If the key is not Enter, or the input field is empty, bail 63 | } 64 | 65 | lang.getObject(this.id + "_listCtrl").addItem(event.target.value); // Add a todo item with the given text 66 | event.target.value = ""; // Clear the input field 67 | event.preventDefault(); 68 | event.stopPropagation(); 69 | } 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /dojo-example/js/todo/ctrl/RouteController.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/router", 4 | "dijit/Destroyable", 5 | "dojox/mvc/_Controller" 6 | ], function(declare, router, Destroyable, _Controller){ 7 | return declare([_Controller, Destroyable], { 8 | // summary: 9 | // A controller that maintains hash attribute in sync with location.hash. 10 | // example: 11 | // In this example, the text box is in sync with the URL hash. 12 | // (The change in URL hash is reflected to the text box, and the edit in text box will be reflected to the URL hash) 13 | // | 14 | // | 15 | // | 19 | // | 20 | // | 21 | // | 22 | // | 23 | // | 25 | // | 26 | // | 27 | 28 | postscript: function(/*Object?*/ params, /*DOMNode?*/ srcNodeRef){ 29 | // summary: 30 | // Kicks off instantiation of this controller, in a similar manner as dijit/_WidgetBase.postscript(). 31 | // params: Object? 32 | // The optional parameters for this controller. 33 | // srcNodeRef: DOMNode? 34 | // The DOM node declaring this controller. Set if this controller is created via Dojo parser. 35 | 36 | this.inherited(arguments); 37 | srcNodeRef && srcNodeRef.setAttribute("widgetId", this.id); // If this is created via Dojo parser, set widgetId attribute so that destroyDescendants() of parent widget works 38 | }, 39 | 40 | startup: function(){ 41 | // summary: 42 | // A function called after the DOM fragment declaring this controller is added to the document, in a similar manner as dijit/_WidgetBase.startup(). 43 | 44 | var _self = this; 45 | this.own(router.register(/.*/, function(e){ // Register a route handling callback for any route, make sure it's cleaned up upon this controller being destroyed 46 | _self._set("hash", e.newPath); // Update hash property 47 | })); 48 | router.startup(); // Activate dojo/router 49 | this.set("hash", router._currentPath); // Set the inital value of hash property 50 | }, 51 | 52 | _setHashAttr: function(value){ 53 | // summary: 54 | // Handler for calls to set("hash", val). 55 | // description: 56 | // If the new value is different from location.hash, updates location.hash. 57 | 58 | if(this.hash != value){ 59 | router.go(value); // If the new value is different from location.hash, updates location.hash 60 | } 61 | this._set("hash", value); // Assign the new value to the property 62 | } 63 | }); 64 | }) 65 | -------------------------------------------------------------------------------- /dojo-example/js/todo/ctrl/TodoListRefController.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/array", 3 | "dojo/_base/declare", 4 | "dojo/Stateful", 5 | "dojox/mvc/ModelRefController" 6 | ], function(array, declare, Stateful, ModelRefController){ 7 | return declare(ModelRefController, { 8 | // summary: 9 | // Our custom controller that does: 10 | // 11 | // - Handle actions like adding/removing/marking 12 | // - Provide references to the todo list in data model, whose data comes from above Dojo Object Store 13 | // 14 | // description: 15 | // The todo list in the data model, which is based on dojox/mvc/StatefulArray, can be referred via this[this._refModelProp]. 16 | // Actions are implemented in the manner of manipulating array. 17 | // The change will automatically be reflected to the UI via the notification system of dojox/mvc/StatefulArray. 18 | 19 | addItem: function(/*String*/ title){ 20 | // summary: 21 | // Adds a todo item with the given title. 22 | // title: String 23 | // The title of todo item. 24 | 25 | this[this._refModelProp].push(new Stateful({title: title, completed: false})); 26 | }, 27 | 28 | removeItem: function(/*String*/ uniqueId){ 29 | // summary: 30 | // Removes a todo item having the given unique ID. 31 | // uniqueId: String 32 | // The unique ID of the todo item to be removed. 33 | 34 | var model = this[this._refModelProp], 35 | indices = array.filter(array.map(model, function(item, idx){ return item.uniqueId == uniqueId ? idx : -1; }), function(idx){ return idx >= 0; }); // The array index of the todo item to bd removed 36 | if(indices.length > 0){ 37 | model.splice(indices[0], 1); 38 | } 39 | }, 40 | 41 | removeCompletedItems: function(){ 42 | // summary: 43 | // Removes todo items that have been marked as complete. 44 | 45 | var model = this[this._refModelProp]; 46 | for(var i = model.length - 1; i >= 0; --i){ 47 | if(model[i].get("completed")){ 48 | model.splice(i, 1); 49 | } 50 | } 51 | }, 52 | 53 | markAll: function(/*Boolean*/ markComplete){ 54 | // summary: 55 | // Mark all todo items as complete or incomplete. 56 | // markComplete: Boolean 57 | // True to mark all todo items as complete. Otherwise to mark all todo items as incomplete. 58 | 59 | array.forEach(this[this._refModelProp], function(item){ 60 | item.set("completed", markComplete); 61 | }); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /dojo-example/js/todo/ctrl/_HashCompletedMixin.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare"], function(declare){ 2 | var ACTIVE = "/active", 3 | COMPLETED = "/completed"; 4 | 5 | function getHiddenState(/*Object*/ props){ 6 | // summary: 7 | // Returns the new hidden state of todo item, given the URL hash and the completed state of todo item. 8 | // props: Object 9 | // An object containing the URL hash and the completed state of todo item. 10 | 11 | return props.hash == ACTIVE ? props.completed : 12 | props.hash == COMPLETED ? !props.completed : 13 | false; 14 | } 15 | 16 | return declare(null, { 17 | // summary: 18 | // A mix-in class for widgets-in-template for todo item, that looks at URL hash and completed state of todo item, and updates the hidden state. 19 | // description: 20 | // A todo item should be hidden if: 21 | // 22 | // - URL hash is "/active" and the todo item is complete -OR- 23 | // - URL hash is "/copleted" and the todo item is incomplete 24 | 25 | _setHashAttr: function(/*String*/ value){ 26 | // summary: 27 | // Handler for calls to set("hash", val), to update hidden state given the new value and the completed state. 28 | 29 | this.set("hidden", getHiddenState({hash: value, completed: this.completed})); // Update hidden state given the new value and the completed state 30 | this._set("hash", value); // Assign the new value to the attribute 31 | }, 32 | 33 | _setCompletedAttr: function(/*Boolean*/ value){ 34 | // summary: 35 | // Handler for calls to set("completed", val), to update hidden state given the new value and the hash. 36 | 37 | this.set("hidden", getHiddenState({hash: this.hash, completed: value})); // Update hidden state given the new value and the hash 38 | this._set("completed", value); // Assign the new value to the attribute 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /dojo-example/js/todo/form/CheckBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * There's an incompatibility between the Dojo CheckBox and the Dojo MVC 3 | * module. To use them together, I've manually tied the "checked" attribute 4 | * value to push updates to the "value" attribute, which the Dojo MVC module 5 | * expects. 6 | */ 7 | define(["dojo/_base/declare", "dijit/form/CheckBox"], function (declare, CheckBox) { 8 | return declare("todo.form.CheckBox", [CheckBox], { 9 | _setCheckedAttr: function (checked) { 10 | this.inherited(arguments); 11 | this._watchCallbacks("value", !checked, checked); 12 | }, 13 | 14 | _getValueAttr: function () { 15 | return this.get("checked"); 16 | } 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /dojo-example/js/todo/form/InlineEditBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extension to the "InlineEditBox" widget to support editing on double click 3 | * events rather than single clicks. We need to override base "postMixInProperties" 4 | * lifecycle method where the event handlers are setup. This method is just a clone 5 | * of that code with the modified event list. 6 | */ 7 | define([ 8 | "dijit/InlineEditBox", 9 | "dojo/_base/declare", 10 | "dojo/_base/lang", 11 | "dojo/dom-class" 12 | ], function (InlineEditBox, declare, lang, domClass) { 13 | 14 | InlineEditBox._InlineEditor.prototype._onChange = function () { 15 | if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){ 16 | this._onBlur(); 17 | } 18 | 19 | }; 20 | return declare("todo.form.InlineEditBox", InlineEditBox, { 21 | 22 | postMixInProperties: function () { 23 | // save pointer to original source node, since Widget nulls-out srcNodeRef 24 | this.displayNode = this.srcNodeRef; 25 | 26 | // connect handlers to the display node 27 | var events = { 28 | ondblclick: "_onClick", 29 | onmouseover: "_onMouseOver", 30 | onmouseout: "_onMouseOut", 31 | onfocus: "_onMouseOver", 32 | onblur: "_onMouseOut" 33 | }; 34 | for(var name in events){ 35 | this.connect(this.displayNode, name, events[name]); 36 | } 37 | this.displayNode.setAttribute("role", "button"); 38 | if(!this.displayNode.getAttribute("tabIndex")){ 39 | this.displayNode.setAttribute("tabIndex", 0); 40 | } 41 | 42 | if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){ 43 | this.value = lang.trim(this.renderAsHtml ? this.displayNode.innerHTML : 44 | (this.displayNode.innerText||this.displayNode.textContent||"")); 45 | } 46 | if(!this.value){ 47 | this.displayNode.innerHTML = this.noValueIndicator; 48 | } 49 | 50 | domClass.add(this.displayNode, 'dijitInlineEditBoxDisplayMode'); 51 | }, 52 | 53 | _onChange: function () { 54 | this.inherited(arguments); 55 | this._onBlur(); 56 | } 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /dojo-example/js/todo/misc/HashSelectedConverter.js: -------------------------------------------------------------------------------- 1 | define({ 2 | // summary: 3 | // A dojox/mvc data converter, that runs between todo/ctrl/HashController and a widget having tag as its DOM node. 4 | // It does one-way conversion from URL hash to boolean state of whether the URL hash matches href attribute of the widget's DOM node. 5 | 6 | format: function(/*String*/ value){ 7 | // summary: 8 | // Returns whether given value matches href attribute of the widget's DOM node. 9 | 10 | return this.target.domNode.getAttribute("href").substr(1) == (value || "/"); 11 | }, 12 | 13 | parse: function(/*Boolean*/ value){ 14 | // summary: 15 | // This functions throws an error so that the new value won't be reflected. 16 | 17 | throw new Error(); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /dojo-example/js/todo/misc/LessThanOrEqualToConverter.js: -------------------------------------------------------------------------------- 1 | define({ 2 | // summary: 3 | // A dojox/mvc data converter, that does one-way conversion that returns whether we have less than n todo items in a specific state, where n is the given number in data converter options. 4 | // Data converter options can be specified by setting constraints property in one of data binding endpoints. 5 | // See data converter section of dojox/mvc/sync library's documentation for more details. 6 | 7 | format: function(/*Number*/ value, /*Object*/ constraints){ 8 | // summary: 9 | // Returns whether given value is less than or equal to the given number in data converter options (default zero). 10 | 11 | return value <= (constraints.lessThanOrEqualTo || 0); 12 | }, 13 | 14 | parse: function(/*Boolean*/ value){ 15 | // summary: 16 | // This functions throws an error so that the new value won't be reflected. 17 | 18 | throw new Error(); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /dojo-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dojo-intern-example", 3 | "description": "An example of using Dojo and Dijit with Intern", 4 | "version": "0.2.0", 5 | "private": true, 6 | "devDependencies": { 7 | "intern": "~4.1.0" 8 | }, 9 | "dependencies": { 10 | "dijit": "~1.12.9", 11 | "dojo": "~1.12.8", 12 | "dojox": "~1.14.6", 13 | "todomvc-common": "~0.1.6" 14 | }, 15 | "scripts": { 16 | "test": "intern" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dojo-example/tests/all.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './model/SimpleTodoModel', 3 | 'dojo/has!host-browser?./store/LocalStorage', 4 | 'dojo/has!host-browser?./form/CheckBox' 5 | ], function () {}); 6 | -------------------------------------------------------------------------------- /dojo-example/tests/form/CheckBox.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'require', 3 | 'todo/form/CheckBox' 4 | ], function (require, CheckBox) { 5 | var registerSuite = intern.getInterface('object').registerSuite; 6 | var assert = intern.getPlugin('chai').assert; 7 | var checkbox; 8 | 9 | registerSuite('CheckBox', { 10 | before: function () { 11 | checkbox = new CheckBox(); 12 | }, 13 | 14 | tests: { 15 | 'get value': function () { 16 | checkbox.set('value', 'arbitraryTitle'); 17 | checkbox.set('checked', true); 18 | assert.strictEqual(checkbox.get('value'), true); 19 | }, 20 | 21 | 'set checked': function () { 22 | checkbox.set('checked', true); 23 | assert.strictEqual(checkbox.get('checked'), true); 24 | } 25 | } 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /dojo-example/tests/functional/Todo.js: -------------------------------------------------------------------------------- 1 | define([ 'dojo/node!@theintern/leadfoot/keys' ], function (keysModule) { 2 | var registerSuite = intern.getInterface('object').registerSuite; 3 | var assert = intern.getPlugin('chai').assert; 4 | var keys = keysModule.default; 5 | 6 | registerSuite('Todo (functional)', { 7 | 'submit form': function () { 8 | return this.remote 9 | .get('index.html') 10 | .setFindTimeout(60000) 11 | .findById('new-todo') 12 | .type('Task 1') 13 | .type(keys.RETURN) 14 | .type('Task 2') 15 | .type(keys.RETURN) 16 | .type('Task 3') 17 | .getSpecAttribute('value') 18 | .then(function (val) { 19 | assert.ok(val.indexOf('Task 3') > -1, 'Task 3 should remain in the new todo'); 20 | }); 21 | } 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /dojo-example/tests/model/SimpleTodoModel.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'require', 3 | 'todo/model/SimpleTodoModel' 4 | ], function (require, SimpleTodoModel) { 5 | var registerSuite = intern.getInterface('object').registerSuite; 6 | var assert = intern.getPlugin('chai').assert; 7 | 8 | registerSuite('SimpleTodoModel', { 9 | 'default data': function () { 10 | var emptyModel = new SimpleTodoModel(); 11 | assert.strictEqual(emptyModel.get('id'), 'todos-dojo', 'Id should default to "todos-dojo"'); 12 | assert.strictEqual(emptyModel.get('todos').length, 0, 'Todos array should default to an empty array.'); 13 | assert.strictEqual(emptyModel.get('incomplete'), 0, 'Incomplete count should default to 0.'); 14 | assert.strictEqual(emptyModel.get('complete'), 0, 'Incomplete count should default to 0.'); 15 | }, 16 | 17 | 'get incomplete (empty model)': function () { 18 | var emptyModel = new SimpleTodoModel(); 19 | emptyModel.todos.push({}); 20 | emptyModel.todos.push({}); 21 | assert.strictEqual(emptyModel.get('incomplete'), 2, 'Prepopulated model todos should determine incomplete model property.'); 22 | emptyModel.set('complete', 2); 23 | assert.strictEqual(emptyModel.get('incomplete'), 0, 'Incomplete count should change when complete count is manually updated.'); 24 | }, 25 | 26 | 'get complete (empty model)': function () { 27 | var emptyModel = new SimpleTodoModel(); 28 | emptyModel.todos.push({}); 29 | emptyModel.todos.push({}); 30 | assert.strictEqual(emptyModel.get('complete'), 0, 'Prepopulated model todos should determine complete model property.'); 31 | emptyModel.set('incomplete', 0); 32 | assert.strictEqual(emptyModel.get('complete'), 2, 'Complete count should change when incomplete count is manually updated.'); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /dojo-example/tests/store/LocalStorage.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'require', 3 | 'todo/store/LocalStorage' 4 | ], function (require, LocalStorage) { 5 | var registerSuite = intern.getInterface('object').registerSuite; 6 | var assert = intern.getPlugin('chai').assert; 7 | var store; 8 | 9 | registerSuite('LocalStorage Store', { 10 | before: function () { 11 | store = new LocalStorage({ 12 | data: [ 13 | {id: 1, name: 'one', prime: false}, 14 | {id: 2, name: 'two', even: true, prime: true}, 15 | {id: 3, name: 'three', prime: true}, 16 | {id: 4, name: 'four', even: true, prime: false}, 17 | {id: 5, name: 'five', prime: true} 18 | ] 19 | }); 20 | }, 21 | 22 | tests: { 23 | get: function () { 24 | assert.strictEqual(store.get(4).name, 'four', 'Store should get correct item based on id.'); 25 | assert.isTrue(store.get(5).prime, 'Store should get correct item based on id.'); 26 | }, 27 | 28 | getIdentity: function () { 29 | var item = store.get(3); 30 | assert.strictEqual(store.getIdentity(item), 3, 'Identifying property (id) should be returned for item.'); 31 | }, 32 | 33 | put: function () { 34 | var four = store.get(4); 35 | four.square = true; 36 | store.put(four); 37 | four = store.get(4); 38 | assert.isTrue(four.square, 'Item should be updated after modification.'); 39 | }, 40 | 41 | add: function () { 42 | store.put({ 43 | id: 6, 44 | perfect: true 45 | }); 46 | assert.isTrue(store.get(6).perfect, 'New item should be added to the store.'); 47 | }, 48 | 49 | query: function () { 50 | var results = store.query({prime: true}); 51 | assert.strictEqual(results.length, 3, 'Three prime results should be found.'); 52 | } 53 | } 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /electron-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-object-rest-spread", 5 | "transform-react-jsx", 6 | "transform-class-properties" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /electron-example/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /electron-example/README.md: -------------------------------------------------------------------------------- 1 | # electron-example 2 | 3 | This example is based on the [React Redux example](https://github.com/reactjs/redux/tree/master/examples/todomvc), 4 | running in Electron. It contains two sets of functional tests, one that uses Intern’s built-in WebDriver library, and 5 | one that uses Spectron. It also contains unit tests, but these are run in Node rather than Electron as Intern 4.x does 6 | not currently support running unit tests in Electron. 7 | 8 | ## Setup 9 | 10 | 1. Install the JRE or JDK. This demo uses Selenium, which requires Java, to run WebDriver tests. 11 | 12 | 2. Install node modules 13 | 14 | ``` 15 | $ npm install 16 | ``` 17 | 18 | 3. Build the example 19 | ``` 20 | $ npm run build 21 | ``` 22 | 23 | ## Running Tests 24 | 25 | **Unit tests (in Node) and functional tests** 26 | 27 | $ npm test 28 | 29 | On Windows, run 30 | 31 | $ npm test config=@windows 32 | 33 | **WebDriver tests using Spectron** 34 | 35 | $ npm test config=@spectron 36 | -------------------------------------------------------------------------------- /electron-example/intern.json: -------------------------------------------------------------------------------- 1 | { 2 | "functionalSuites": "tests/functional/app.js", 3 | "node": { 4 | "suites": "tests/unit/**/*.js", 5 | "plugins": ["tests/build_check.js", "tests/pre.js"] 6 | }, 7 | "environments": [ 8 | "node", 9 | { 10 | "browserName": "chrome", 11 | "fixSessionCapabilities": false, 12 | "chromeOptions": { 13 | "binary": "{pwd}/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron", 14 | "args": ["app={pwd}/build/bootstrap.js"] 15 | } 16 | } 17 | ], 18 | "tunnelOptions": { 19 | "version": "3.4.0", 20 | "drivers": [ 21 | { 22 | "name": "chrome", 23 | "version": "2.24" 24 | } 25 | ] 26 | }, 27 | "filterErrorStack": true, 28 | "configs": { 29 | "spectron": { 30 | "suites": "tests/functional/spectron.js", 31 | "environments": [], 32 | "node": { 33 | "suites": [] 34 | } 35 | }, 36 | 37 | "windows": { 38 | "environments": [ 39 | "node", 40 | { 41 | "browserName": "chrome", 42 | "fixSessionCapabilities": false, 43 | "chromeOptions": { 44 | "binary": "{pwd}/node_modules/electron/dist/electron.exe", 45 | "args": ["app={pwd}/build/bootstrap.js"] 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /electron-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "electron-example", 4 | "version": "0.1.0", 5 | "description": "An example showing how to use Intern to test an Electron app", 6 | "homepage": "./", 7 | "dependencies": { 8 | "babel-core": "~6.23.0", 9 | "babel-plugin-transform-class-properties": "~6.23.0", 10 | "babel-plugin-transform-object-rest-spread": "~6.23.0", 11 | "babel-plugin-transform-react-jsx": "~6.23.0", 12 | "babel-preset-env": "~1.3.3", 13 | "classnames": "~2.2.5", 14 | "electron": "~1.6.11", 15 | "react": "~15.5.0", 16 | "react-dom": "~15.5.0", 17 | "react-redux": "~5.0.0", 18 | "redux": "~3.6.0", 19 | "todomvc-app-css": "~2.0.6" 20 | }, 21 | "devDependencies": { 22 | "enzyme": "~2.8.0", 23 | "intern": "~4.3.0", 24 | "react-addons-test-utils": "~15.5.0", 25 | "react-scripts": "~0.9.3", 26 | "react-test-renderer": "~15.5.0", 27 | "serve": "~5.1.1", 28 | "shx": "~0.3.2", 29 | "spectron": "~3.7.2" 30 | }, 31 | "scripts": { 32 | "build": "react-scripts build && shx cp src/main.js build && shx cp src/bootstrap.js build", 33 | "start": "cd build && electron bootstrap.js", 34 | "clean": "shx rm -rf build", 35 | "test": "intern" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /electron-example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux TodoMVC Example 7 | 8 | 9 |
      10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /electron-example/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | 3 | export const addTodo = text => ({ type: types.ADD_TODO, text }) 4 | export const deleteTodo = id => ({ type: types.DELETE_TODO, id }) 5 | export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text }) 6 | export const completeTodo = id => ({ type: types.COMPLETE_TODO, id }) 7 | export const completeAll = () => ({ type: types.COMPLETE_ALL }) 8 | export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED }) 9 | -------------------------------------------------------------------------------- /electron-example/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | process.chdir(__dirname); 3 | require('./main.js'); 4 | -------------------------------------------------------------------------------- /electron-example/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import classnames from 'classnames' 3 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 4 | 5 | const FILTER_TITLES = { 6 | [SHOW_ALL]: 'All', 7 | [SHOW_ACTIVE]: 'Active', 8 | [SHOW_COMPLETED]: 'Completed' 9 | } 10 | 11 | export default class Footer extends Component { 12 | static propTypes = { 13 | completedCount: PropTypes.number.isRequired, 14 | activeCount: PropTypes.number.isRequired, 15 | filter: PropTypes.string.isRequired, 16 | onClearCompleted: PropTypes.func.isRequired, 17 | onShow: PropTypes.func.isRequired 18 | } 19 | 20 | renderTodoCount() { 21 | const { activeCount } = this.props 22 | const itemWord = activeCount === 1 ? 'item' : 'items' 23 | 24 | return ( 25 | 26 | {activeCount || 'No'} {itemWord} left 27 | 28 | ) 29 | } 30 | 31 | renderFilterLink(filter) { 32 | const title = FILTER_TITLES[filter] 33 | const { filter: selectedFilter, onShow } = this.props 34 | 35 | return ( 36 |
      onShow(filter)}> 39 | {title} 40 | 41 | ) 42 | } 43 | 44 | renderClearButton() { 45 | const { completedCount, onClearCompleted } = this.props 46 | if (completedCount > 0) { 47 | return ( 48 | 52 | ) 53 | } 54 | } 55 | 56 | render() { 57 | return ( 58 |
      59 | {this.renderTodoCount()} 60 |
        61 | {[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter => 62 |
      • 63 | {this.renderFilterLink(filter)} 64 |
      • 65 | )} 66 |
      67 | {this.renderClearButton()} 68 |
      69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /electron-example/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import TodoTextInput from './TodoTextInput' 3 | 4 | export default class Header extends Component { 5 | static propTypes = { 6 | addTodo: PropTypes.func.isRequired 7 | } 8 | 9 | handleSave = text => { 10 | if (text.length !== 0) { 11 | this.props.addTodo(text) 12 | } 13 | } 14 | 15 | render() { 16 | return ( 17 |
      18 |

      todos

      19 | 22 |
      23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /electron-example/src/components/MainSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import TodoItem from './TodoItem' 3 | import Footer from './Footer' 4 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 5 | 6 | const TODO_FILTERS = { 7 | [SHOW_ALL]: () => true, 8 | [SHOW_ACTIVE]: todo => !todo.completed, 9 | [SHOW_COMPLETED]: todo => todo.completed 10 | } 11 | 12 | export default class MainSection extends Component { 13 | static propTypes = { 14 | todos: PropTypes.array.isRequired, 15 | actions: PropTypes.object.isRequired 16 | } 17 | 18 | state = { filter: SHOW_ALL } 19 | 20 | handleClearCompleted = () => { 21 | this.props.actions.clearCompleted() 22 | } 23 | 24 | handleShow = filter => { 25 | this.setState({ filter }) 26 | } 27 | 28 | renderToggleAll(completedCount) { 29 | const { todos, actions } = this.props 30 | if (todos.length > 0) { 31 | return ( 32 | 36 | ) 37 | } 38 | } 39 | 40 | renderFooter(completedCount) { 41 | const { todos } = this.props 42 | const { filter } = this.state 43 | const activeCount = todos.length - completedCount 44 | 45 | if (todos.length) { 46 | return ( 47 |