├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.custom-filters.md
├── README.md
├── dist
├── index.js
└── index.js.map
├── easeljs
├── build.js
└── easel-header.js
├── elixir.js
├── gulpfile.js
├── karma.conf.js
├── package.json
├── src
├── components
│ ├── EaselBitmap.vue
│ ├── EaselCanvas.vue
│ ├── EaselContainer.vue
│ ├── EaselShape.vue
│ ├── EaselSprite.vue
│ ├── EaselSpriteSheet.vue
│ └── EaselText.vue
├── filters.js
├── filters
│ ├── ColorMatrixFilter.js
│ ├── FilterSet.js
│ └── PixelStrokeFilter.js
├── index.js
├── libs
│ ├── Events.js
│ ├── PromiseParty.js
│ ├── easel-event-binder.js
│ ├── get-dimensions-from-get-bounds.js
│ ├── normalize-alignment.js
│ └── sort-by-dom.js
└── mixins
│ ├── EaselAlign.js
│ ├── EaselCache.js
│ ├── EaselDisplayObject.js
│ ├── EaselFilter.js
│ └── EaselParent.js
└── test
├── ColorMatrixFilter.spec.js
├── EaselBitmap.spec.js
├── EaselCanvas.spec.js
├── EaselContainer.spec.js
├── EaselDisplayObject.spec.js
├── EaselShape.spec.js
├── EaselSprite.spec.js
├── EaselSpriteSheet.spec.js
├── EaselText.spec.js
├── Events.spec.js
├── FilterSet.spec.js
├── PixelStrokeFilter.spec.js
├── PromiseParty.spec.js
├── easel-event-binder.spec.js
├── fixtures
├── EaselFake.js
└── Set.js
├── images
├── gulfstream_park.jpg
└── lastguardian-all.png
├── includes
├── can-cache.js
├── can-filter.js
├── does-events.js
├── is-a-display-object.js
├── is-alignable.js
└── is-an-easel-parent.js
├── normalize-alignment.spec.js
├── sort-by-dom.spec.js
└── test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-object-rest-spread"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | sftp-config.json
2 | node_modules
3 | easeljs/easel.js
4 | package-lock.json
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .git
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 dankuck
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.custom-filters.md:
--------------------------------------------------------------------------------
1 | ## Custom filters
2 |
3 | Create new filters by registering a class with the VueEaseljs library at
4 | runtime using the `VueEaseljs.registerFilter` method.
5 |
6 | | Parameter | |
7 | | ----- | ---- |
8 | | name | the name for the filter |
9 | | Filter | the class that filters |
10 |
11 | When the filter is used in an element's `filters` prop, the extra values are
12 | passed to the filter's constructor method.
13 |
14 | The filter should have one of two methods: either `adjustContext` or
15 | `adjustImageData`.
16 |
17 | ### adjustContext
18 |
19 | | Parameter | |
20 | | --------- | --- |
21 | | ctx | a CanvasRenderingContext2D that contains the visual element |
22 | | x | the x coordinate of the element on ctx |
23 | | y | the y coordinate of the element on ctx |
24 | | width | the width of the element on ctx |
25 | | height | the height of the element on ctx |
26 | | targetCtx | the CanvasRenderingContext2D to draw to, if absent, use ctx |
27 | | targetX | the x coordinate to draw to, if absent, use x |
28 | | targetY | the y coordinate to draw to, if absent, use y |
29 |
30 | This method should make changes to the data in `ctx` and write them to
31 | `targetCtx` if present, or else back to `ctx`.
32 |
33 | This method must return `true` if it succeeded.
34 |
35 | ### adjustImageData
36 |
37 | | Parameter | |
38 | | --------- | --- |
39 | | imageData | an ImageData object |
40 |
41 | This method should make changes directly to the `imageData` object.
42 |
43 | This method must return `true` if it succeeded.
44 |
45 | Example:
46 | ```
47 | const VueEaseljs = require('vue-easeljs');
48 |
49 | class MyFilter {
50 |
51 | constructor(value1, value2) {
52 | ...
53 | }
54 |
55 | adjustContext(ctx, x, y, width, height, targetCtx, targetX, targetY) {
56 | ...
57 | }
58 | }
59 |
60 | VueEaseljs.registerFilter('MyFilter', MyFilter);
61 | ```
62 |
--------------------------------------------------------------------------------
/easeljs/build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run this file to create easel.js by combining the easeljs repo files with
3 | * the header that makes it a proper ES6 module.
4 | *
5 | * This file is run automatically during `npm install`.
6 | */
7 |
8 | const fs = require('fs');
9 |
10 | /**
11 | * Only run in dev mode. In dev mode we install easeljs. Nobody else needs it.
12 | */
13 | if (fs.existsSync(`${__dirname}/../node_modules/easeljs`)) {
14 | const header = fs.readFileSync(`${__dirname}/easel-header.js`);
15 | const body = fs.readFileSync(`${__dirname}/../node_modules/easeljs/lib/easeljs.js`);
16 | const whole = header + body;
17 | fs.writeFileSync(`${__dirname}/easel.js`, whole);
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/easeljs/easel-header.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This header gets attached to the code from the easeljs repo to create a
3 | * full, importable module.
4 | */
5 |
6 | const createjs = this.createjs = this.createjs || {};
7 |
8 | export default createjs;
9 |
10 | // If any content follows this line, it comes from the easeljs repo.
11 | // ----------------------------------------------------------------------------
12 |
13 |
--------------------------------------------------------------------------------
/elixir.js:
--------------------------------------------------------------------------------
1 |
2 | var elixir = require('laravel-elixir');
3 |
4 | elixir.ready(function () {
5 | elixir.webpack.mergeConfig({
6 | devtool: 'source-map',
7 | // ensure we are using the version of Vue that supports templates
8 | resolve: {
9 | alias: {
10 | vue: 'vue/dist/vue.common.js'
11 | },
12 | extensions: ['.js', '.vue']
13 | },
14 | vue: {
15 | buble: {
16 | objectAssign: 'Object.assign'
17 | }
18 | },
19 | module: {
20 | loaders: [
21 | {
22 | test: /\.vue$/,
23 | loader: 'vue-loader'
24 | },
25 | {
26 | test: /\.js$/,
27 | loader: 'buble-loader',
28 | query: {
29 | objectAssign: 'Object.assign',
30 | },
31 | },
32 | {
33 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
34 | loader: 'file-loader',
35 | query: {
36 | limit: 10000,
37 | name: '../img/[name].[hash:7].[ext]'
38 | }
39 | },
40 | {
41 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
42 | loader: 'url-loader',
43 | query: {
44 | limit: 10000,
45 | name: '../fonts/[name].[hash:7].[ext]'
46 | }
47 | }
48 | ]
49 | }
50 | })
51 | });
52 |
53 | module.exports = elixir;
54 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var webpack = require('webpack');
3 |
4 | var elixir = require('./elixir.js');
5 |
6 |
7 | elixir.ready(function () {
8 | elixir.webpack.mergeConfig({
9 | output: {
10 | libraryTarget: 'commonjs',
11 | },
12 | plugins: [
13 | new webpack.optimize.UglifyJsPlugin()
14 | ]
15 | });
16 | });
17 |
18 | elixir(function (mix) {
19 | mix.webpack('index.js', './dist', './src');
20 | });
21 |
22 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 |
2 | var elixir = require('./elixir.js');
3 |
4 | var webpackConfig;
5 |
6 | elixir(function (mix) {
7 | webpackConfig = elixir.webpack.config;
8 | });
9 |
10 | module.exports = function (config) {
11 | config.set({
12 | browsers: ['PhantomJS'],
13 | frameworks: ['mocha'],
14 | files: [
15 | 'test/test.js',
16 | {
17 | pattern: 'test/images/*',
18 | included: false,
19 | served: true,
20 | },
21 | ],
22 | preprocessors: {
23 | 'test/test.js': ['webpack'],
24 | },
25 | reporters: ['spec'],
26 | webpack: webpackConfig,
27 | webpackMiddleware: {
28 | noInfo: true,
29 | },
30 | singleRun: true,
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-easeljs",
3 | "version": "0.1.14",
4 | "description": "A Vue.js plugin to control an HTML5 canvas using EaselJS",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "karma start karma.conf.js",
8 | "build": "gulp",
9 | "postinstall": "node ./easeljs/build.js"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/dankuck/vue-easeljs.git"
14 | },
15 | "contributors": {
16 | "name": "Dan Kuck-Alvarez",
17 | "email": "dankuck@gmail.com",
18 | "url": "http://www.dankuck.com/"
19 | },
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/dankuck/vue-easeljs/issues"
23 | },
24 | "homepage": "https://github.com/dankuck/vue-easeljs#readme",
25 | "devDependencies": {
26 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
27 | "easeljs": "^1.0.2",
28 | "gulp": "^3.9.1",
29 | "karma": "^1.7.0",
30 | "karma-mocha": "^1.3.0",
31 | "karma-phantomjs-launcher": "^1.0.4",
32 | "karma-spec-reporter": "0.0.32",
33 | "karma-webpack": "^2.0.4",
34 | "laravel-elixir": "^6.0.0-15",
35 | "laravel-elixir-vue-2": "^0.3.0",
36 | "laravel-elixir-webpack-official": "^1.0.10",
37 | "lodash.findindex": "^4.6.0",
38 | "lodash.intersection": "^4.4.0",
39 | "lodash.shuffle": "^4.2.0",
40 | "mocha": "^3.2.0",
41 | "phantomjs-prebuilt": "^2.1.14",
42 | "vue": "^2.1.*",
43 | "babel-plugin-transform-object-rest-spread": "^6.26.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/EaselBitmap.vue:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/src/components/EaselCanvas.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
142 |
--------------------------------------------------------------------------------
/src/components/EaselContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
40 |
--------------------------------------------------------------------------------
/src/components/EaselShape.vue:
--------------------------------------------------------------------------------
1 |
105 |
--------------------------------------------------------------------------------
/src/components/EaselSprite.vue:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/src/components/EaselSpriteSheet.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
29 |
--------------------------------------------------------------------------------
/src/components/EaselText.vue:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/src/filters.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../easeljs/easel.js';
2 | import FilterSet from './filters/FilterSet.js';
3 | import ColorMatrixFilter from './filters/ColorMatrixFilter.js';
4 | import PixelStrokeFilter from './filters/PixelStrokeFilter.js';
5 |
6 | const filters = new FilterSet();
7 |
8 | filters.register('BlurFilter', easeljs.BlurFilter);
9 | filters.register('ColorFilter', easeljs.ColorFilter);
10 | filters.register('ColorMatrixFilter', ColorMatrixFilter);
11 | filters.register('PixelStrokeFilter', PixelStrokeFilter);
12 |
13 | export default filters;
14 |
--------------------------------------------------------------------------------
/src/filters/ColorMatrixFilter.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../../easeljs/easel.js';
2 |
3 | /**
4 | |------------------------
5 | | ColorMatrixFilter
6 | |------------------------
7 | | A version of the ColorMatrixFilter that accepts scalar constructor
8 | | parameters for ease-of-use.
9 | |
10 | | The constructor creates a ColorMatrix using the constructor params and
11 | | passes it to the EaselJS ColorMatrixFilter constructor.
12 | */
13 | export default class ColorMatrixFilter extends easeljs.ColorMatrixFilter {
14 |
15 | constructor(brightness, contrast, saturation, hue) {
16 | const matrix = new easeljs.ColorMatrix(brightness, contrast, saturation, hue);
17 | easeljs.ColorMatrixFilter.apply(this, [matrix]);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/filters/FilterSet.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../../easeljs/easel.js';
2 |
3 | export default class FilterSet {
4 |
5 | constructor() {
6 | this.filters = [];
7 | }
8 |
9 | register(name, Filter) {
10 | if (Filter.prototype.applyFilter) {
11 | this.filters[name] = Filter;
12 | } else {
13 | const prototype = Filter.prototype || Filter.constructor.prototype;
14 | if (prototype.adjustContext) {
15 | prototype.usesContext = true;
16 | prototype.applyFilter = function (ctx, x, y, w, h, tctx, tx, ty) {
17 | return this.adjustContext(ctx, x, y, w, h, tctx, tx, ty);
18 | };
19 | } else if (prototype.adjustImageData) {
20 | prototype.usesContext = false;
21 | prototype._applyFilter = function (imageData) {
22 | return this.adjustImageData(imageData);
23 | };
24 | } else {
25 | throw new Error('Incompatible filter');
26 | }
27 | for (let field in easeljs.Filter.prototype) {
28 | if (!prototype[field]) {
29 | prototype[field] = easeljs.Filter.prototype[field];
30 | }
31 | }
32 | this.filters[name] = Filter;
33 | }
34 | }
35 |
36 | build(filterArray) {
37 | const filterName = filterArray[0];
38 | const args = [null, ...filterArray.slice(1)];
39 | const Filter = this.filters[filterName];
40 | if (!Filter) {
41 | throw new Error(`No such filter registered: ${filterName}`);
42 | }
43 | return new (Function.prototype.bind.apply(Filter, args));
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/src/filters/PixelStrokeFilter.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../../easeljs/easel.js';
2 |
3 | /**
4 | |------------------------
5 | | PixelStrokeFilter
6 | |------------------------
7 | | Add a stroke to an element
8 | */
9 |
10 | const calculatedBrushes = {};
11 |
12 | export default class PixelStrokeFilter {
13 |
14 | constructor(stroke = [], size = 1, options = {}) {
15 | this.strokeRed = stroke[0] || 0;
16 | this.strokeGreen = stroke[1] || 0;
17 | this.strokeBlue = stroke[2] || 0;
18 | this.strokeAlpha = stroke[3] || 255;
19 | this.size = size;
20 | this.brush = this.calculateBrush(size);
21 | this.alphaCache = {};
22 | this.options = options;
23 | }
24 |
25 | adjustImageData(imageData) {
26 | const {strokeRed, strokeGreen, strokeBlue, strokeAlpha} = this;
27 | const antiAlias = typeof this.options.antiAlias === 'undefined' ? true : this.options.antiAlias;
28 | const {data, width, height} = imageData;
29 | const length = data.length;
30 | const copy = data.slice(0);
31 | const activatePixel = (x, y, a) => {
32 | if (x < 0 || y < 0 || x >= width || y >= height) {
33 | return;
34 | }
35 | const i = (y * width + x) * 4;
36 | const alpha = antiAlias
37 | ? this.alphaCache[a] || (this.alphaCache[a] = Math.round(Math.floor(strokeAlpha * a)))
38 | : strokeAlpha;
39 | if (!copy[i + 3] && data[i + 3] < alpha) {
40 | data[i + 0] = strokeRed;
41 | data[i + 1] = strokeGreen;
42 | data[i + 2] = strokeBlue;
43 | data[i + 3] = alpha;
44 | }
45 | };
46 | const applyBrush = (sx, sy) => {
47 | for (let i = 0; i < this.brush.length; i++) {
48 | const {y, minX, maxX, a} = this.brush[i];
49 | activatePixel(sx + minX, sy + y, a);
50 | activatePixel(sx + maxX, sy + y, a);
51 | for (let j = minX + 1; j <= maxX - 1; j++) {
52 | activatePixel(sx + j, sy + y, 1);
53 | }
54 | }
55 | };
56 | for (let x = 0; x < width; x++) {
57 | for (let y = 0; y < height; y++) {
58 | if (copy[(y * width + x) * 4 + 3] > 0) {
59 | applyBrush(x, y);
60 | }
61 | }
62 | }
63 | return true;
64 | }
65 |
66 | calculateBrush(size) {
67 | if (calculatedBrushes[size]) {
68 | return calculatedBrushes[size];
69 | }
70 | /*
71 | Imagine a circle like this
72 | 1 | 2
73 | \ /
74 | 8 3
75 | - -
76 | 7 4
77 | / \
78 | 6 | 5
79 | */
80 | const map = {};
81 | // Figure out sector 4
82 | for (let y = 0; y <= size; y++) {
83 | const tan = y / size;
84 | const angle = Math.atan(tan);
85 | const cos = Math.cos(angle);
86 | const sin = Math.sin(angle);
87 | const realX = cos * size;
88 | const realY = sin * size;
89 | const ceilX = Math.ceil(realX);
90 | const ceilY = Math.ceil(realY);
91 | const intensityX = realX - Math.floor(realX);
92 | const intensityY = realY - Math.floor(realY);
93 | let intensity = (intensityX + intensityY) / 2;
94 | if (intensity === 0) {
95 | intensity = 1;
96 | }
97 | map[ceilY] = {
98 | x: ceilX,
99 | y: ceilY,
100 | a: intensity,
101 | };
102 | }
103 | // Flip sector 4's x and y to get sector 5
104 | for (let field in map) {
105 | const {x: y, y: x, a} = map[field];
106 | if (!map[y]) {
107 | map[y] = {
108 | x,
109 | y,
110 | a,
111 | };
112 | }
113 | }
114 | // Use that to build horizontal lines
115 | // from 7 to 4
116 | // from 6 to 5
117 | // from 1 to 2
118 | // from 8 to 3
119 | const lines = [];
120 | for (let field in map) {
121 | const {x, y, a} = map[field];
122 | lines.push({
123 | y,
124 | minX: -x,
125 | maxX: x,
126 | a,
127 | });
128 | if (y !== 0) {
129 | lines.push({
130 | y: -y,
131 | minX: -x,
132 | maxX: x,
133 | a,
134 | });
135 | }
136 | }
137 | calculatedBrushes[size] = lines;
138 | return calculatedBrushes[size];
139 | }
140 |
141 | getBounds(rect = null) {
142 | return (rect || new easeljs.Rectangle()).pad(this.size * 2, this.size * 2, this.size * 2, this.size * 2);
143 | }
144 | };
145 |
146 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../easeljs/easel.js';
2 | import filters from './filters.js';
3 |
4 | module.exports = {
5 | createjs: easeljs,
6 | easeljs: easeljs,
7 | EaselBitmap: require('./components/EaselBitmap.vue'),
8 | EaselCanvas: require('./components/EaselCanvas.vue'),
9 | EaselContainer: require('./components/EaselContainer.vue'),
10 | EaselShape: require('./components/EaselShape.vue'),
11 | EaselSprite: require('./components/EaselSprite.vue'),
12 | EaselSpriteSheet: require('./components/EaselSpriteSheet.vue'),
13 | EaselText: require('./components/EaselText.vue'),
14 | install(Vue) {
15 | Vue.component('easel-bitmap', this.EaselBitmap);
16 | Vue.component('easel-canvas', this.EaselCanvas);
17 | Vue.component('easel-container', this.EaselContainer);
18 | Vue.component('easel-shape', this.EaselShape);
19 | Vue.component('easel-sprite', this.EaselSprite);
20 | Vue.component('easel-sprite-sheet', this.EaselSpriteSheet);
21 | Vue.component('easel-text', this.EaselText);
22 | },
23 | registerFilter(...args) {
24 | return filters.register(...args);
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/libs/Events.js:
--------------------------------------------------------------------------------
1 | /**
2 | |---------------------
3 | | Events
4 | |---------------------
5 | | You need a basic event library. Here it is.
6 | |
7 | | Call events.on(string, Function) when you want it to send events
8 | | to .
9 | |
10 | | Call events.off(string, Function) when you want it to stop.
11 | |
12 | | Call events.fire(string, ...args) to call all the callbacks for
13 | | with the arguments that follow it.
14 | |
15 | */
16 | export default class Events {
17 |
18 | constructor({errorCode} = {}) {
19 | this.callbacks = [];
20 | this.errorCode = errorCode;
21 | }
22 |
23 | on(event, cb) {
24 | this.callbacks.push({event, cb});
25 | }
26 |
27 | off(removeEvent, removeCb) {
28 | this.callbacks = this.callbacks
29 | .filter(({event, cb}) => ! (event === removeEvent && cb === removeCb));
30 | }
31 |
32 | fire(fireEvent, ...args) {
33 | this.callbacks
34 | .filter(({event}) => event === fireEvent)
35 | .forEach(({cb}) => {
36 | try {
37 | cb(...args)
38 | } catch (e) {
39 | if (this.errorCode && fireEvent !== this.errorCode) {
40 | this.fire(this.errorCode, fireEvent, args, cb, e);
41 | }
42 | }
43 | });
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/src/libs/PromiseParty.js:
--------------------------------------------------------------------------------
1 | import Events from './Events';
2 |
3 | /**
4 | |-------------------
5 | | PromiseParty
6 | |-------------------
7 | | Send your Promises to a party and keep track of how many are there. Promises
8 | | leave the party when they resolve or reject.
9 | |
10 | | When a Promise is added, an 'add' event is fired, with the number of
11 | | Promises currently at the party.
12 | |
13 | | When a Promise completes (either resolves or rejects), a 'remove' event is
14 | | fired, with the number of Promises currently at the party.
15 | |
16 | | In both cases, a 'change' event is fired, and wouldn't you know it, it also
17 | | includes the number of Promises currently at the party.
18 | |
19 | | Use `on(, )` to listen to events.
20 | |
21 | | Use `off(, )` when you don't want to listen anymore.
22 | |
23 | | Use `add(Promise)` to send a new Promise to the party.
24 | |
25 | */
26 | export default class PromiseParty {
27 |
28 | constructor() {
29 | this.events = new Events({errorCode: 'error'});
30 | this.promises = new Set();
31 | }
32 |
33 | add(promise) {
34 | if (!promise.finally) {
35 | throw new Error('We only accept promises here');
36 | }
37 |
38 | this.promises.add(promise);
39 | this.events.fire('add', this.promises.size);
40 | this.events.fire('change', this.promises.size);
41 |
42 | promise.finally(() => {
43 | this.promises.delete(promise);
44 | this.events.fire('remove', this.promises.size);
45 | this.events.fire('change', this.promises.size);
46 | });
47 | }
48 |
49 | on(event, cb) {
50 | this.events.on(event, cb);
51 | return this;
52 | }
53 |
54 | off(event, cb) {
55 | this.events.off(event, cb);
56 | return this;
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/libs/easel-event-binder.js:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | easel-event-binder.js
4 | |--------------------------------------------------------------------------
5 | |
6 | | Binds all requested EaselJS events to a Vue component
7 | |
8 | */
9 |
10 | // Need to add and test these events for Canvas
11 | // * drawend
12 | // * drawstart
13 | // * mouseenter
14 | // * mouseleave
15 | // * stagemousedown
16 | // * stagemousemove
17 | // * stagemouseup
18 | // * tickend
19 | // * tickstart
20 |
21 | import intersection from 'lodash.intersection';
22 |
23 | export const eventTypes = [
24 | 'added',
25 | 'animationend',
26 | 'change',
27 | 'click',
28 | 'dblclick',
29 | 'mousedown',
30 | 'mouseout',
31 | 'mouseover',
32 | 'pressmove',
33 | 'pressup',
34 | 'removed',
35 | 'rollout',
36 | 'rollover',
37 | 'tick',
38 | ];
39 |
40 | const componentDemandsEventType = function (component, eventType) {
41 | return Boolean(
42 | component.$options._parentListeners
43 | && component.$options._parentListeners[eventType]
44 | );
45 | };
46 |
47 | const augmentEvent = function (component, event) {
48 | event.component = component;
49 | if (component.easelCanvas && component.easelCanvas.augmentEvent) {
50 | event = component.easelCanvas.augmentEvent(event);
51 | }
52 | return event;
53 | };
54 |
55 | export default {
56 | bindEvents(vueComponent, easelComponent) {
57 | eventTypes.forEach(eventType => {
58 | if (!componentDemandsEventType(vueComponent, eventType)) {
59 | return;
60 | }
61 | easelComponent.addEventListener(
62 | eventType,
63 | (event) => vueComponent.$emit(eventType, augmentEvent(vueComponent, event))
64 | );
65 | });
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/src/libs/get-dimensions-from-get-bounds.js:
--------------------------------------------------------------------------------
1 |
2 | export default function getDimensionsFromGetBounds(component) {
3 | return new Promise((resolve, error) => {
4 | const getBounds = () => {
5 | try {
6 | if (!component.component) {
7 | // Component is uninitialized or went away, abandon.
8 | clearInterval(waiting);
9 | reject('No component available to getBounds');
10 | } else if (component.component.getBounds()) {
11 | // Got the bounds, resolve with them
12 | clearInterval(waiting);
13 | const {x, y, width, height} = component.component.getBounds();
14 | resolve({x, y, width, height});
15 | }
16 | // else keep waiting...
17 | } catch (e) {
18 | // trouble! quit trying
19 | clearInterval(waiting);
20 | throw e;
21 | }
22 | }
23 | const waiting = setInterval(getBounds, 10);
24 | getBounds();
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/src/libs/normalize-alignment.js:
--------------------------------------------------------------------------------
1 |
2 | export const horizontalValues = ['', 'left', 'center', 'right', 'start', 'end'];
3 | export const verticalValues = ['', 'top', 'center', 'bottom', 'hanging', 'middle', 'alphabetic', 'ideographic'];
4 |
5 | const isHorizontal = value => horizontalValues.indexOf(value) > -1;
6 | const isVertical = value => verticalValues.indexOf(value) > -1;
7 |
8 | export default function normalizeAlignment(alignment) {
9 | if (typeof alignment === 'string') {
10 | alignment = alignment.trim().split(/\-/);
11 | }
12 | const [first, second] = alignment;
13 | if (isHorizontal(first) && isVertical(second)) {
14 | return [first, second];
15 | }
16 | if (isVertical(first) && isHorizontal(second)) {
17 | return [second, first];
18 | }
19 | throw new Error(`Illegal alignment, bad mix of values or unknown value in: ${first}, ${second}`);
20 | };
21 |
--------------------------------------------------------------------------------
/src/libs/sort-by-dom.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | |----------------------------
4 | | sort-by-dom.js
5 | |----------------------------
6 | | Given an array of Vue components, this will sort them in the same order
7 | | their elements are in the DOM.
8 | |
9 | */
10 |
11 | export default function sortByDom(components) {
12 | return [...components].sort(sorter);
13 | };
14 |
15 | export function sorter(a, b) {
16 | const compare = a.$el.compareDocumentPosition(b.$el);
17 | if (compare & Node.DOCUMENT_POSITION_DISCONNECTED) {
18 | throw new Error('Nodes are not in the same tree');
19 | }
20 | if (compare & Node.DOCUMENT_POSITION_CONTAINS) {
21 | // b contains a, b should come first
22 | return 1;
23 | }
24 | if (compare & Node.DOCUMENT_POSITION_CONTAINED_BY) {
25 | // a contains b, a should come first
26 | return -1;
27 | }
28 | if (compare & Node.DOCUMENT_POSITION_PRECEDING) {
29 | // b precedes a, b should come first
30 | return 1;
31 | }
32 | if (compare & Node.DOCUMENT_POSITION_FOLLOWING) {
33 | // a precedes b, a should come first
34 | return -1;
35 | }
36 | // default to No Change
37 | return 0;
38 | };
39 |
--------------------------------------------------------------------------------
/src/mixins/EaselAlign.js:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | EaselAlign
4 | |--------------------------------------------------------------------------
5 | | This mixin provides alignment support to a component. It handles the
6 | | `align` prop.
7 | |
8 | | Any component mixing this in should also mix in EaselDisplayObject.
9 | |
10 | | A component that mixes this in should provide:
11 | | * getAlignDimensions - A method that returns a Promise that resolves with
12 | | an object formatted as `{x, y, width, height}`.
13 | |
14 | */
15 |
16 | import easeljs from '../../easeljs/easel.js';
17 | import normalizeAlignment from '../libs/normalize-alignment.js';
18 |
19 | export default {
20 | props: ['align'],
21 | watch: {
22 | align() {
23 | if (this.component) {
24 | this.updateAlign();
25 | }
26 | },
27 | },
28 | mounted() {
29 | this.$watch('component', () => this.updateAlign());
30 | },
31 | computed: {
32 | /**
33 | * Normalizes the `align` prop's value by ensuring it is an array and
34 | * horizontal value comes before vertical value.
35 | * @return {Array}
36 | */
37 | normalizedAlign() {
38 | return normalizeAlignment(this.align || ['', '']);
39 | },
40 | },
41 | methods: {
42 | /**
43 | * Sets the offset values for this element to those set by the align
44 | * prop. Returns a Promise that resolves with dimensions that were
45 | * passed to this method.
46 | * @return Promise
47 | */
48 | updateAlign() {
49 | return this.remainInvisibleUntil(
50 | this.getAlignDimensions()
51 | // .then((dimensions) => this.$nextTick().then(() => dimensions))
52 | .then(
53 | dimensions => {
54 | const w = dimensions.width,
55 | h = dimensions.height,
56 | hAlign = this.normalizedAlign[0] || 'left',
57 | vAlign = this.normalizedAlign[1] || 'top';
58 | if (hAlign === 'left') {
59 | this.component.regX = 0;
60 | } else if (hAlign === 'center') {
61 | this.component.regX = w / 2;
62 | } else if (hAlign === 'right') {
63 | this.component.regX = w;
64 | }
65 | if (vAlign === 'top') {
66 | this.component.regY = 0;
67 | } else if (vAlign === 'center') {
68 | this.component.regY = h / 2;
69 | } else if (vAlign === 'bottom') {
70 | this.component.regY = h;
71 | }
72 | return dimensions;
73 | },
74 | error => {
75 | console.error('Cannot align:', error);
76 | throw error;
77 | }
78 | )
79 | );
80 | },
81 | /**
82 | * Returns a Promise that resolves with an object like
83 | * `{width, height}` so that alignment can be calculated. Subclasses
84 | * must define this method.
85 | * @return {Object}
86 | */
87 | getAlignDimensions() {
88 | // Components should override this
89 | throw new Error('EaselAlign components must define a `getAlignDimensions` method');
90 | },
91 | },
92 | };
93 |
--------------------------------------------------------------------------------
/src/mixins/EaselCache.js:
--------------------------------------------------------------------------------
1 | /**
2 | |-----------------------------------------------------------------------------
3 | | EaselCache
4 | |-----------------------------------------------------------------------------
5 | | This mixin provides cache support to a component. It handles the `cache`
6 | | prop at initialization and any updates.
7 | |
8 | | It operates transparently, so it should be impossible to tell it's active
9 | | except that the code may operate more speedily.
10 | |
11 | | Any component mixing this in should also mix in EaselDisplayObject.
12 | |
13 | | A component that mixes this in should provide:
14 | | * updatesEaselCache - A top-level Array of the names of props or properties
15 | | to watch for changes that should trigger a cache
16 | | refresh
17 | | * getCacheBounds - A method that returns a Promise which resolves with
18 | | an object formatted as `{x, y, width, height}`.
19 | |
20 | */
21 |
22 | export default {
23 | props: ['cache'],
24 | /**
25 | * Components that mix this in should replace this with an array of props
26 | * or properties that should trigger cache refreshes when they change.
27 | * @type {Array}
28 | */
29 | updatesEaselCache: ['scale'],
30 | data() {
31 | return {
32 | cacheStarted: false,
33 | cacheNeedsUpdate: false,
34 | beforeCaches: [],
35 | cacheWhens: [],
36 | };
37 | },
38 | mounted() {
39 | this.updateCacheOnChange = () => {
40 | this.cacheNeedsUpdate = true;
41 | this.setParentCacheNeedsUpdate();
42 | };
43 | const setupOnChange = () => {
44 | if (this.component) {
45 | this.component.on('change', this.updateCacheOnChange);
46 | }
47 | };
48 | window.addEventListener('resize', this.updateCacheOnChange);
49 | setupOnChange();
50 | this.$watch('component', setupOnChange);
51 | this.$options.updatesEaselCache.forEach(prop => {
52 | this.$watch(prop, () => this.cacheNeedsUpdate = true);
53 | });
54 | Object.keys(this.$options.props).forEach(prop => {
55 | this.$watch(prop, () => this.setParentCacheNeedsUpdate());
56 | });
57 | this.$nextTick(() => this.cacheInit());
58 | },
59 | destroyed() {
60 | window.removeEventListener('resize', this.updateCacheOnChange);
61 | },
62 | watch: {
63 | shouldCache() {
64 | if (this.shouldCache) {
65 | this.cacheInit();
66 | } else {
67 | this.cacheDestroy();
68 | }
69 | },
70 | cacheNeedsUpdate() {
71 | if (this.cacheNeedsUpdate && this.shouldCache) {
72 | this.$nextTick(() => {
73 | if (this.component && this.component.cacheCanvas) {
74 | this.cacheDestroy();
75 | this.cacheInit();
76 | // Didn't use updateCache() because it has a bug in
77 | // which it gives a new cache the same size as the
78 | // existing cache.
79 | }
80 | });
81 | }
82 | },
83 | },
84 | computed: {
85 | shouldCache() {
86 | return this.cache
87 | || this.cacheWhens.reduce((result, callback) => result || callback(), false);
88 | },
89 | cacheScale() {
90 | let scale = this.scale || 1;
91 | let parent = this.easelParent;
92 | while (parent) {
93 | if (parent.viewportScale) {
94 | scale *= parent.viewportScale.scaleX;
95 | } else {
96 | scale *= parent.scale || 1;
97 | }
98 | parent = parent.easelParent;
99 | }
100 | return scale;
101 | },
102 | },
103 | methods: {
104 | beforeCache(callback) {
105 | this.beforeCaches.push(callback);
106 | },
107 | triggerBeforeCaches() {
108 | this.beforeCaches.forEach(callback => callback());
109 | },
110 | cacheWhen(callback) {
111 | this.cacheWhens.push(callback);
112 | },
113 | cacheInit() {
114 | if (this.shouldCache) {
115 | this.getCacheBounds()
116 | .then(({x, y, width, height}) => {
117 | this.triggerBeforeCaches();
118 |
119 | this.easelCanvas.createCanvas(() => {
120 | this.component.cache(x, y, width, height, this.cacheScale * window.devicePixelRatio);
121 | });
122 | this.cacheStarted = true;
123 | this.cacheNeedsUpdate = false;
124 | })
125 | .catch((error) => console.error(`Cannot cache: ${error}`, error));
126 | }
127 | },
128 | cacheDestroy() {
129 | this.component.uncache();
130 | this.cacheStarted = false;
131 | this.cacheNeedsUpdate = false;
132 | },
133 | setParentCacheNeedsUpdate() {
134 | if (this.easelParent && 'cacheNeedsUpdate' in this.easelParent) {
135 | this.easelParent.cacheNeedsUpdate = true;
136 | }
137 | },
138 | /**
139 | * Get the bounds of a rectangle containing the element. This must be
140 | * defined by the subclass. It must return `{x, y, width, height}`.
141 | * These values are passed directly to EaselJS's cache() method. See
142 | * its documentation.
143 | * @return {Object}
144 | */
145 | getCacheBounds() {
146 | return Promise.reject('EaselCache components must define a `getCacheBounds` method');
147 | },
148 | /**
149 | * Get the cache bounds as they would be seen from the parent's
150 | * perspective, and large enough to contain the element rotated to any
151 | * degree along with its shadow, if any.
152 | * EaselContainer uses this to calculate its own cache bounds.
153 | * @return {object}
154 | */
155 | getRelativeCacheBounds() {
156 | return this.getCacheBounds()
157 | .then(bounds => {
158 | const x = ((this.x || 0) - this.component.regX) + bounds.x;
159 | const y = ((this.y || 0) - this.component.regY) + bounds.y;
160 | return {
161 | x,
162 | y,
163 | width: bounds.width,
164 | height: bounds.height,
165 | };
166 | })
167 | .then(bounds => this.expandForShadow(bounds))
168 | .then(bounds => this.getSmallestSquare(bounds))
169 | },
170 | /**
171 | * Return the bounds of the smallest square that can contain the given
172 | * bounds rotated to any degree.
173 | * @param {Object} bounds
174 | * @return {Object}
175 | */
176 | getSmallestSquare({x, y, width, height}) {
177 | // Use width and height as the legs of a right triangle and figure
178 | // out the hypotenuse. Thanks, Pythagoras.
179 | // The hypotenuse is the longest possible length that the object
180 | // could extend.
181 | const hypotenuse = Math.sqrt(
182 | Math.pow(width, 2)
183 | + Math.pow(height, 2)
184 | );
185 | return {
186 | x: -hypotenuse,
187 | y: -hypotenuse,
188 | width: hypotenuse * 2,
189 | height: hypotenuse * 2,
190 | };
191 |
192 | },
193 | /**
194 | * Return the smallest bounds that can contain both bounds
195 | * @param {Object} a
196 | * @param {Object} b
197 | * @return {Object}
198 | */
199 | getSmallestCombination(a, b) {
200 | const x = Math.min(a.x, b.x);
201 | const y = Math.min(a.y, b.y);
202 | const width = Math.max(
203 | (b.x - x) + b.width,
204 | (a.x - x) + a.width
205 | );
206 | const height = Math.max(
207 | (b.y - y) + b.height,
208 | (a.y - y) + a.height
209 | );
210 | return {
211 | x,
212 | y,
213 | width,
214 | height,
215 | };
216 | },
217 | /**
218 | * If a shadow exists, add its dimensions as padding on all sides
219 | * @return {Object}
220 | */
221 | expandForShadow(bounds) {
222 | if (!this.shadow) {
223 | return bounds;
224 | }
225 | // Expand bounds to cover the shadow offsets and blurriness
226 | // in every direction. Needs to be every direction, since
227 | // rotation is applied before shadow.
228 | const [color, offsetX, offsetY, blurriness] = this.shadow;
229 | // Find the longest possible edge and expand the bounds in
230 | // every direction. We cannot be less naive, because we
231 | // want to account for every rotation.
232 | const padding = Math.max(offsetX, offsetY) + blurriness;
233 | return {
234 | x: bounds.x - padding,
235 | y: bounds.y - padding,
236 | width: bounds.width + padding * 2,
237 | height: bounds.height + padding * 2,
238 | };
239 | },
240 | },
241 | };
242 |
--------------------------------------------------------------------------------
/src/mixins/EaselDisplayObject.js:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | EaselDisplayObject
4 | |--------------------------------------------------------------------------
5 | |
6 | | This mixin gives an Easel Vue component the required elements to be
7 | | visible on the canvas.
8 | |
9 | */
10 |
11 | import EaselEventBinder from '../libs/easel-event-binder.js';
12 | import easeljs from '../../easeljs/easel.js';
13 | import PromiseParty from '../libs/PromiseParty.js';
14 |
15 | const passthroughProps = ['rotation', 'cursor', 'name'];
16 |
17 | export default {
18 | inject: ['easelParent', 'easelCanvas'],
19 | props: {
20 | x: {},
21 | y: {},
22 | flip: {},
23 | rotation: {},
24 | scale: {},
25 | alpha: {},
26 | shadow: {},
27 | cursor: {},
28 | visible: {
29 | default: true,
30 | },
31 | name: {},
32 | },
33 | data() {
34 | return {
35 | component: null,
36 | forceInvisiblePromises: new PromiseParty()
37 | .on('change', count => this.forceInvisible = count > 0),
38 | forceInvisible: false,
39 | };
40 | },
41 | mounted() {
42 | this.$watch('component', (now, old) => {
43 | if (old) {
44 | this.displayObjectBreakdown(old);
45 | }
46 | if (now) {
47 | this.displayObjectInit();
48 | }
49 | });
50 | // These just get copied directly onto the component; no funny business
51 | passthroughProps.forEach(prop => {
52 | this.$watch(prop, () => {
53 | if (this.component) {
54 | this.component[prop] = this[prop];
55 | }
56 | });
57 | });
58 | this.$watch('x', () => {
59 | if (this.component) {
60 | this.component.x = this.x || 0;
61 | }
62 | });
63 | this.$watch('y', () => {
64 | if (this.component) {
65 | this.component.y = this.y || 0;
66 | }
67 | });
68 | this.$watch('flip', () => {
69 | if (this.component) {
70 | this.updateScales();
71 | }
72 | });
73 | this.$watch('scale', () => {
74 | if (this.component) {
75 | this.updateScales();
76 | }
77 | });
78 | this.$watch('alpha', () => {
79 | if (this.component) {
80 | this.updateAlpha();
81 | }
82 | });
83 | this.$watch('shadow', () => {
84 | if (this.component) {
85 | this.updateShadow();
86 | }
87 | });
88 | this.$watch('shouldBeVisible', () => {
89 | if (this.component) {
90 | this.updateVisibility();
91 | }
92 | });
93 | },
94 | computed: {
95 | shouldBeVisible() {
96 | return this.visible && !this.forceInvisible;
97 | },
98 | },
99 | destroyed() {
100 | this.displayObjectBreakdown();
101 | },
102 | methods: {
103 | displayObjectInit() {
104 | EaselEventBinder.bindEvents(this, this.component);
105 | this.component.x = this.x || 0;
106 | this.component.y = this.y || 0;
107 | passthroughProps.forEach(prop => {
108 | this.component[prop] = this[prop];
109 | });
110 | this.updateScales();
111 | this.updateAlpha();
112 | this.updateShadow();
113 | this.updateVisibility();
114 | this.easelParent.addChild(this);
115 | },
116 | displayObjectBreakdown(easelComponent = null) {
117 | this.easelParent.removeChild(this, easelComponent);
118 | },
119 | updateScales() {
120 | if (this.component) {
121 | const scale = this.scale || 1;
122 | this.component.scaleX = this.flip === 'horizontal' || this.flip === 'both' ? -scale : scale;
123 | this.component.scaleY = this.flip === 'vertical' || this.flip === 'both' ? -scale : scale;
124 | }
125 | },
126 | updateAlpha() {
127 | this.component.alpha = isNaN(this.alpha) || this.alpha === null ? 1 : this.alpha;
128 | },
129 | updateShadow() {
130 | if (this.shadow) {
131 | this.component.shadow = new easeljs.Shadow(this.shadow[0], this.shadow[1], this.shadow[2], this.shadow[3]);
132 | } else {
133 | this.component.shadow = null;
134 | }
135 | },
136 | updateVisibility() {
137 | this.component.visible = this.shouldBeVisible;
138 | },
139 | /**
140 | * Force visible = false until this promise has resolved or rejected.
141 | * Returns a Promise that resolves when the given one does.
142 | * @param {Promise} promise
143 | * @return {Promise}
144 | */
145 | remainInvisibleUntil(promise) {
146 | this.forceInvisiblePromises.add(promise);
147 | return promise;
148 | },
149 | },
150 | };
151 |
--------------------------------------------------------------------------------
/src/mixins/EaselFilter.js:
--------------------------------------------------------------------------------
1 | import easeljs from '../../easeljs/easel.js';
2 | import filters from '../filters.js';
3 |
4 | const build = filters.build.bind(filters);
5 |
6 | export default {
7 | props: ['filters'],
8 | mounted() {
9 | this.cacheWhen(() => this.filters && this.filters.length > 0);
10 | this.beforeCache(() => {
11 | if (this.filters && this.filters.length > 0) {
12 | this.component.filters = this.filters.map(build);
13 | } else {
14 | this.component.filters = null;
15 | }
16 | });
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/src/mixins/EaselParent.js:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | EaselParent
4 | |--------------------------------------------------------------------------
5 | |
6 | | This mixin lets a Vue component act as a container for an Easel Vue
7 | | component.
8 | |
9 | */
10 |
11 | import easeljs from '../../easeljs/easel.js';
12 | import sortByDom from '../libs/sort-by-dom.js';
13 | import findIndex from 'lodash.findindex';
14 |
15 | export default {
16 | provide() {
17 | return {
18 | easelParent: this,
19 | };
20 | },
21 | data() {
22 | return {
23 | // not guaranteed to be in order
24 | children: [],
25 | };
26 | },
27 | updated() {
28 | // runs when the DOM changes
29 | this.$nextTick(() => this.syncEaselChildren());
30 | },
31 | watch: {
32 | children() {
33 | this.syncEaselChildren();
34 | },
35 | },
36 | methods: {
37 | syncEaselChildren() {
38 | if (this.component) {
39 | sortByDom(this.children).forEach((vueChild, i) => {
40 | const atPosition = this.component.numChildren >= i ? this.component.getChildAt(i) : null;
41 | if (vueChild.component === atPosition) {
42 | // already there
43 | return;
44 | }
45 | this.component.addChildAt(vueChild.component, i);
46 | });
47 | }
48 | },
49 | addChild(vueChild) {
50 | if (!this.hasChild(vueChild)) {
51 | this.children.push(vueChild);
52 | }
53 | },
54 | removeChild(vueChild, easelComponent = null) {
55 | const index = this.indexOfChild(vueChild);
56 | if (index < 0) {
57 | return false;
58 | }
59 | this.children.splice(index, 1);
60 | if (this.component) {
61 | this.component.removeChild(easelComponent || vueChild.component);
62 | }
63 | return true;
64 | },
65 | hasChild(vueChild) {
66 | return this.indexOfChild(vueChild) > -1;
67 | },
68 | indexOfChild(vueChild) {
69 | return findIndex(this.children, vueChild);
70 | },
71 | },
72 | };
73 |
--------------------------------------------------------------------------------
/test/ColorMatrixFilter.spec.js:
--------------------------------------------------------------------------------
1 | import ColorMatrixFilter from '../src/filters/ColorMatrixFilter.js';
2 | import easeljs from '../easeljs/easel.js';
3 | import assert from 'assert';
4 | const {deepStrictEqual: equal} = assert;
5 |
6 | describe('ColorMatrixFilter', function () {
7 |
8 | it('instantiates', function () {
9 | new ColorMatrixFilter();
10 | });
11 |
12 | it('has a ColorMatrix', function () {
13 | const filter = new ColorMatrixFilter(1, 2, 3, 4);
14 | assert(filter.matrix);
15 | equal(filter.matrix.toArray(), new easeljs.ColorMatrix(1, 2, 3, 4).toArray());
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/EaselBitmap.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import EaselBitmap from '../src/components/EaselBitmap.vue';
4 | import isADisplayObject from './includes/is-a-display-object.js';
5 | import isAlignable from './includes/is-alignable.js';
6 | import canCache from './includes/can-cache.js';
7 | import canFilter from './includes/can-filter.js';
8 |
9 | describe('EaselBitmap', function () {
10 |
11 | describe('is a display object that', isADisplayObject(EaselBitmap, 'image="/base/test/images/gulfstream_park.jpg"'));
12 |
13 | describe('is cacheable and', canCache(EaselBitmap, {}, [
14 | {
15 | name: 'image',
16 | value: '/base/test/images/gulfstream_park.jpg',
17 | changeTo: '/base/test/images/lastguardian-all.png',
18 | shouldUpdateSameObject: true,
19 | },
20 | ]));
21 |
22 | describe('is alignable and', isAlignable(EaselBitmap, {width: 1500, height: 946}, 'image="/base/test/images/gulfstream_park.jpg"'));
23 |
24 | describe('can filter and', canFilter(EaselBitmap, 'image="/base/test/images/gulfstream_park.jpg"'));
25 |
26 | const buildVm = function () {
27 | const easel = {
28 | addChild(vueChild) {
29 | },
30 | removeChild(vueChild) {
31 | },
32 | };
33 |
34 | const vm = new Vue({
35 | template: `
36 |
37 |
44 |
45 |
46 | `,
47 | provide() {
48 | return {
49 | easelParent: easel,
50 | easelCanvas: easel,
51 | };
52 | },
53 | data() {
54 | return {
55 | image: '/base/test/images/gulfstream_park.jpg',
56 | showBitmap: true,
57 | align: 'top-left',
58 | };
59 | },
60 | components: {
61 | 'easel-bitmap': EaselBitmap,
62 | },
63 | }).$mount();
64 |
65 | const bitmap = vm.$refs.bitmap;
66 |
67 | return {vm, bitmap};
68 | };
69 |
70 | it('should exist', function () {
71 | const {vm, bitmap} = buildVm();
72 | assert(bitmap);
73 | });
74 |
75 | it('should have component field', function () {
76 | const {vm, bitmap} = buildVm();
77 | assert(bitmap.component);
78 | });
79 |
80 | it('should have the right image', function () {
81 | const {vm, bitmap} = buildVm();
82 | assert(/gulfstream_park/.test(bitmap.component.image.src), 'Wrong src: ' + bitmap.component.image.src);
83 | });
84 |
85 | it('should be able to change the image', function (done) {
86 | const {vm, bitmap} = buildVm();
87 | const image = vm.image;
88 | vm.image = Math.random();
89 | Vue.nextTick()
90 | .then(() => {
91 | const qr = new RegExp(vm.image);
92 | assert(
93 | qr.test(bitmap.component.image.src) || qr.test(bitmap.component.image),
94 | 'Wrong src in: ' + bitmap.component.image
95 | );
96 | })
97 | .then(done, done);
98 | });
99 |
100 | it('should get dimensions', function (done) {
101 | const {vm, bitmap} = buildVm();
102 | bitmap.getAlignDimensions()
103 | .then(dimensions => {
104 | assert(dimensions.width === 1500, 'Wrong width: ' + dimensions.width);
105 | assert(dimensions.height === 946, 'Wrong height: ' + dimensions.height);
106 | })
107 | .then(done, done);
108 | });
109 |
110 | ['center-left', 'top-left', 'bottom-right']
111 | .forEach(align => {
112 | it('should get cache bounds (no matter the align)', function (done) {
113 | const {vm, bitmap} = buildVm();
114 | vm.align = align;
115 | Vue.nextTick()
116 | .then(() => bitmap.getCacheBounds())
117 | .then(({x, y, width, height}) => {
118 | assert(x === 0, `x is wrong: ${x}`);
119 | assert(y === 0, `y is wrong: ${y}`);
120 | assert(width === 1500, `width is wrong: ${width}`);
121 | assert(height === 946, `height is wrong: ${height}`);
122 | })
123 | .then(done, done);
124 | });
125 | });
126 |
127 | it('updates alignment on image change', function (done) {
128 | const {vm, bitmap} = buildVm();
129 | assert(bitmap.component.regX === 0, 'regX wrong at start');
130 | assert(bitmap.component.regY === 0, 'regY wrong at start');
131 | vm.align = 'center-center';
132 | const image = new Image();
133 | image.src = '/base/test/images/lastguardian-all.png';
134 | image.addEventListener('load', function () {
135 | vm.image = image;
136 | Vue.nextTick()
137 | .then(() => {
138 | assert(bitmap.component.regX > 0, `regX now: ${bitmap.component.regX}`);
139 | assert(bitmap.component.regY > 0, `regY now: ${bitmap.component.regY}`);
140 | })
141 | .then(done, done);
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/test/EaselCanvas.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import EaselCanvas from '../src/components/EaselCanvas.vue';
4 | import easeljs from '../easeljs/easel.js';
5 | import isAnEaselParent from './includes/is-an-easel-parent.js';
6 | import doesEvents from './includes/does-events.js';
7 |
8 | describe('EaselCanvas', function () {
9 |
10 | describe('is an easel parent that', isAnEaselParent(EaselCanvas));
11 |
12 | describe('does events and', doesEvents(EaselCanvas));
13 |
14 | const buildVm = function () {
15 | const vm = new Vue({
16 | template: `
17 |
26 |
27 |
28 | `,
29 | data() {
30 | return {
31 | eventLog: [],
32 | antiAlias: true,
33 | height: 300,
34 | width: 400,
35 | vheight: null,
36 | vwidth: null,
37 | };
38 | },
39 | components: {
40 | 'easel-canvas': EaselCanvas,
41 | },
42 | methods: {
43 | logEvent(event) {
44 | this.eventLog.push(event);
45 | },
46 | clearEventLog() {
47 | this.eventLog = [];
48 | },
49 | },
50 | }).$mount();
51 |
52 | const canvas = vm.$refs.easelCanvas;
53 |
54 | return {vm, canvas};
55 | };
56 |
57 | it('should have a canvas object', function () {
58 | const {vm, canvas} = buildVm();
59 | assert(vm.$el.nodeName === 'CANVAS');
60 | });
61 |
62 | it('should have the slot stuff we put in', function () {
63 | const {vm, canvas} = buildVm();
64 | assert(vm.$el.querySelector('#im-in-a-slot'));
65 | });
66 |
67 | it('should have an easel object with a component object', function () {
68 | const {vm, canvas} = buildVm();
69 | assert(canvas.component);
70 | });
71 |
72 | it('should have a component that calls update on its own', function (done) {
73 | const {vm, canvas} = buildVm();
74 | const update = canvas.component.update;
75 | canvas.component.update = function (event) {
76 | assert(event);
77 | canvas.component.update = update;
78 | done();
79 | };
80 | });
81 |
82 | it('should be able to anti-alias', function (done) {
83 | const {vm, canvas} = buildVm();
84 | Vue.nextTick()
85 | .then(() => {
86 | assert(canvas.context.imageSmoothingEnabled === true, 'Not smoothing: ' + canvas.context.imageSmoothingEnabled);
87 | })
88 | .then(done, done);
89 | });
90 |
91 | it('should not use anti-alias', function (done) {
92 | const {vm, canvas} = buildVm();
93 | vm.antiAlias = false;
94 | Vue.nextTick()
95 | .then(() => {
96 | assert(canvas.context.imageSmoothingEnabled === false, 'Smoothing, but should not: ' + canvas.context.imageSmoothingEnabled);
97 | })
98 | .then(done, done);
99 | });
100 |
101 | it('should keep anti-alias off after resize', function (done) {
102 | const {vm, canvas} = buildVm();
103 | vm.antiAlias = false;
104 | Vue.nextTick()
105 | .then(() => {
106 | assert(canvas.context.imageSmoothingEnabled === false, 'Smoothing, but should not: ' + canvas.context.imageSmoothingEnabled);
107 | canvas.context.imageSmoothingEnabled = true;
108 | window.dispatchEvent(new Event('resize'));
109 | return Vue.nextTick();
110 | })
111 | .then(() => {
112 | assert(canvas.context.imageSmoothingEnabled === false, 'Smoothing again, but should not: ' + canvas.context.imageSmoothingEnabled);
113 | })
114 | .then(done, done);
115 | });
116 |
117 | it('should default anti-alias to true', function (done) {
118 | const {vm, canvas} = buildVm();
119 | vm.antiAlias = undefined;
120 | Vue.nextTick()
121 | .then(() => {
122 | assert(canvas.context.imageSmoothingEnabled === true, 'Not smoothing, but should: ' + canvas.context.imageSmoothingEnabled);
123 | })
124 | .then(done, done);
125 | });
126 |
127 | it('should provide canvases to any code it wraps', function () {
128 | const {vm, canvas} = buildVm();
129 | canvas.createCanvas(() => {
130 | assert(easeljs.createCanvas, 'createCanvas does not exist');
131 | });
132 | assert(!easeljs.createCanvas, 'createCanvas exists');
133 | });
134 |
135 | it('should provide different canvases to any code it wraps twice', function () {
136 | const {vm, canvas} = buildVm();
137 | let firstCreateCanvas,
138 | secondCreateCanvas,
139 | firstCreateCanvasAgain;
140 | canvas.createCanvas(() => {
141 | firstCreateCanvas = easeljs.createCanvas;
142 | canvas.createCanvas(() => {
143 | secondCreateCanvas = easeljs.createCanvas;
144 | });
145 | firstCreateCanvasAgain = easeljs.createCanvas;
146 | });
147 | assert(firstCreateCanvas, 'firstCreateCanvas should exist');
148 | assert(secondCreateCanvas, 'secondCreateCanvas should exist');
149 | assert(firstCreateCanvasAgain, 'firstCreateCanvasAgain should exist');
150 | assert(secondCreateCanvas !== firstCreateCanvas, '1st and 2nd should not be the same');
151 | assert(firstCreateCanvas === firstCreateCanvasAgain, '1st and 3rd should be the same');
152 | assert(!easeljs.createCanvas, 'createCanvas should not exist');
153 | });
154 |
155 | it('should scale to device pixel ratio', function () {
156 | window.devicePixelRatio = 2;
157 | const {vm, canvas} = buildVm();
158 | const htmlCanvas = canvas.$refs.canvas;
159 | assert(htmlCanvas.width === 800, `${htmlCanvas.width} !== 800`);
160 | assert(htmlCanvas.height === 600, `${htmlCanvas.height} !== 600`);
161 | assert(htmlCanvas.style.width === '400px', `${htmlCanvas.style.width} !== 400px`);
162 | assert(htmlCanvas.style.height === '300px', `${htmlCanvas.style.height} !== 300px`);
163 | assert(canvas.component.scale === 2, `${canvas.component.scale} !== 2`);
164 | });
165 |
166 | it('should rescale on device pixel ratio change', function (done) {
167 | window.devicePixelRatio = 2;
168 | const {vm, canvas} = buildVm();
169 | const htmlCanvas = canvas.$refs.canvas;
170 | window.devicePixelRatio = 3;
171 | window.dispatchEvent(new Event('resize'));
172 | Vue.nextTick()
173 | .then(() => {
174 | assert(htmlCanvas.width === 1200, `${htmlCanvas.width} !== 1200`);
175 | assert(htmlCanvas.height === 900, `${htmlCanvas.height} !== 900`);
176 | assert(htmlCanvas.style.width === '400px', `${htmlCanvas.style.width} !== 400px`);
177 | assert(htmlCanvas.style.height === '300px', `${htmlCanvas.style.height} !== 300px`);
178 | assert(canvas.component.scale === 3, `${canvas.component.scale} !== 3`);
179 | })
180 | .then(done, done);
181 | });
182 |
183 | it('should rescale on width and height change', function (done) {
184 | window.devicePixelRatio = 2;
185 | const {vm, canvas} = buildVm();
186 | const htmlCanvas = canvas.$refs.canvas;
187 | vm.height = 301;
188 | Vue.nextTick()
189 | .then(() => {
190 | assert(htmlCanvas.width === 800, `${htmlCanvas.width} !== 800`);
191 | assert(htmlCanvas.height === 602, `${htmlCanvas.height} !== 602`);
192 | assert(htmlCanvas.style.width === '400px', `${htmlCanvas.style.width} !== 400px`);
193 | assert(htmlCanvas.style.height === '301px', `${htmlCanvas.style.height} !== 301px`);
194 | assert(canvas.component.scale === 2, `${canvas.component.scale} !== 2`);
195 | vm.width = 401;
196 | return Vue.nextTick();
197 | })
198 | .then(() => {
199 | assert(htmlCanvas.width === 802, `${htmlCanvas.width} !== 802`);
200 | assert(htmlCanvas.height === 602, `${htmlCanvas.height} !== 602`);
201 | assert(htmlCanvas.style.width === '401px', `${htmlCanvas.style.width} !== 401px`);
202 | assert(htmlCanvas.style.height === '301px', `${htmlCanvas.style.height} !== 301px`);
203 | assert(canvas.component.scale === 2, `${canvas.component.scale} !== 2`);
204 | })
205 | .then(done, done);
206 | });
207 |
208 | it('should have viewport-height and viewport-width', function (done) {
209 | window.devicePixelRatio = 1;
210 | const {vm, canvas} = buildVm();
211 | vm.vheight = 300;
212 | vm.vwidth = 400;
213 | Vue.nextTick()
214 | .then(() => {
215 | assert(canvas.viewportHeight === 300);
216 | assert(canvas.viewportWidth === 400);
217 | assert(canvas.component.scaleY === 1);
218 | assert(canvas.component.scaleX === 1);
219 | })
220 | .then(done, done);
221 | });
222 |
223 | it('should change scaleX and scaleY with viewport-height and viewport-width', function (done) {
224 | window.devicePixelRatio = 1;
225 | const {vm, canvas} = buildVm();
226 | vm.vheight = 600; // twice the canvas height
227 | vm.vwidth = 200; // half the canvas width
228 | Vue.nextTick()
229 | .then(() => {
230 | assert(canvas.component.scaleY === .5, JSON.stringify([canvas.viewport, canvas.viewportScale, canvas.component.scaleY, canvas.viewportHeight]));
231 | assert(canvas.component.scaleX === 2);
232 | })
233 | .then(done, done);
234 | });
235 |
236 | it('should change scaleX and scaleY with viewport-height and viewport-width', function (done) {
237 | window.devicePixelRatio = 1;
238 | const {vm, canvas} = buildVm();
239 | // cause scale to double
240 | vm.vheight = 150;
241 | vm.vwidth = 200;
242 | Vue.nextTick()
243 | .then(() => {
244 | // cause scale to double again
245 | window.devicePixelRatio = 2;
246 | window.dispatchEvent(new Event('resize'));
247 | return Vue.nextTick();
248 | })
249 | .then(() => {
250 | assert(canvas.component.scaleY === 4);
251 | assert(canvas.component.scaleX === 4);
252 | })
253 | .then(done, done);
254 | });
255 |
256 | it('should have touch', function () {
257 | const Touch = easeljs.Touch;
258 | let sawEnable, sawDisable;
259 | easeljs.Touch = {
260 | enable(component) {
261 | sawEnable = component;
262 | },
263 | disable(component) {
264 | sawDisable = component;
265 | },
266 | };
267 | const vm = new Vue({
268 | template: `
269 |
270 | `,
271 | components: {
272 | 'easel-canvas': EaselCanvas,
273 | },
274 | }).$mount();
275 | vm.$destroy();
276 | assert(sawEnable, 'did not see enable');
277 | assert(sawDisable, 'did not see disable');
278 | easeljs.Touch = Touch;
279 | });
280 |
281 | it('should augment events', function () {
282 | window.devicePixelRatio = 2;
283 | const {vm, canvas} = buildVm();
284 | const event = canvas.augmentEvent({
285 | stageX: 100,
286 | stageY: 200,
287 | });
288 | assert(event.viewportX === 50);
289 | assert(event.viewportY === 100);
290 | });
291 | });
292 |
--------------------------------------------------------------------------------
/test/EaselContainer.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import EaselContainer from '../src/components/EaselContainer.vue';
3 | import easeljs from '../easeljs/easel.js';
4 | import Vue from 'vue';
5 | import isAnEaselParent from './includes/is-an-easel-parent.js';
6 | import EaselFake from './fixtures/EaselFake.js';
7 | import isADisplayObject from './includes/is-a-display-object.js';
8 | import canCache from './includes/can-cache.js';
9 | import canFilter from './includes/can-filter.js';
10 | const {deepStrictEqual: equal} = assert;
11 |
12 | describe('EaselContainer', function () {
13 |
14 | describe('is an easel parent that', isAnEaselParent(EaselContainer));
15 |
16 | describe('is a display object that', isADisplayObject(EaselContainer));
17 |
18 | describe('is cacheable and', canCache(EaselContainer, {}, []));
19 |
20 | describe('can filter and', canFilter(EaselContainer));
21 |
22 | const buildVm = function () {
23 | const easel = {
24 | addChild(vueChild) {
25 | },
26 | removeChild(vueChild) {
27 | },
28 | };
29 |
30 | const vm = new Vue({
31 | template: `
32 |
33 |
39 |
40 |
41 | `,
42 | provide() {
43 | return {
44 | easelParent: easel,
45 | easelCanvas: easel,
46 | };
47 | },
48 | data() {
49 | return {
50 | showFake: true,
51 | x: 3,
52 | y: 4,
53 | shadow: null,
54 | };
55 | },
56 | components: {
57 | 'easel-fake': EaselFake,
58 | 'easel-container': EaselContainer,
59 | },
60 | }).$mount();
61 |
62 | const container = vm.$refs.container;
63 | const fake = vm.$refs.fake;
64 |
65 | return {vm, container, fake};
66 | };
67 |
68 | it('should exist', function () {
69 | const {vm, container, fake} = buildVm();
70 | assert(container);
71 | });
72 |
73 | it('should have an easel', function () {
74 | const {vm, container, fake} = buildVm();
75 | assert(container.easelParent);
76 | });
77 |
78 | it('should have component field', function () {
79 | const {vm, container, fake} = buildVm();
80 | assert(container.component);
81 | });
82 |
83 | it('should be the parent of the fake', function (done) {
84 | const {vm, container, fake} = buildVm();
85 | Vue.nextTick()
86 | .then(() => {
87 | assert(fake.component.parent === container.component);
88 | })
89 | .then(done, done);
90 | });
91 |
92 | it('should get cache dimensions including the fake', function (done) {
93 | const {vm, container, fake} = buildVm();
94 | Vue.nextTick()
95 | .then(() => {
96 | vm.x = 0;
97 | vm.y = 0;
98 | return Vue.nextTick();
99 | })
100 | .then(() => {
101 | return Promise.all([
102 | fake.getCacheBounds(),
103 | container.getCacheBounds(),
104 | ]);
105 | })
106 | .then(([fakeBounds, containerBounds]) => {
107 | // from EaselFake fixture
108 | equal(
109 | {
110 | x: -10,
111 | y: -20,
112 | width: 30,
113 | height: 40,
114 | },
115 | fakeBounds
116 | );
117 | // The square that surrounds a rotating EaselFake
118 | // The square is calculated using the hypotenuse of the above
119 | // rectangle.
120 | // √(30² + 40²) = 50, a super-convenient exact number
121 | equal(
122 | {
123 | x: -50,
124 | y: -50,
125 | width: 100,
126 | height: 100,
127 | },
128 | {
129 | x: containerBounds.x,
130 | y: containerBounds.y,
131 | width: containerBounds.width,
132 | height: containerBounds.height,
133 | }
134 | );
135 | })
136 | .then(done, done);
137 | });
138 |
139 | it('should get cache dimensions including the shadow', function (done) {
140 | const {vm, container, fake} = buildVm();
141 | Vue.nextTick()
142 | .then(() => {
143 | vm.x = 0;
144 | vm.y = 0;
145 | vm.shadow = ['black', 5, 10, 5];
146 | return Vue.nextTick();
147 | })
148 | .then(() => {
149 | return Promise.all([
150 | fake.getCacheBounds(),
151 | container.getCacheBounds(),
152 | ]);
153 | })
154 | .then(([fakeBounds, containerBounds]) => {
155 | // from EaselFake fixture
156 | equal(
157 | {
158 | x: -10,
159 | y: -20,
160 | width: 30,
161 | height: 40,
162 | },
163 | fakeBounds
164 | );
165 | // The square that surrounds a rotating EaselFake
166 | // First the shadow's padding is added to all sides.
167 | // The shadow padding is done by adding its longest offset and
168 | // the blurriness amount. In this case 10 and 5. Since it's on
169 | // all sides, it's applied twice to width and height. That
170 | // makes an addition of 30 to both.
171 | // The square is calculated using the hypotenuse of the
172 | // resulting rectangle.
173 | // √((30 + 30)² + (40 + 30)²) ~= 92
174 | equal(
175 | {
176 | x: -92,
177 | y: -92,
178 | width: 184,
179 | height: 184,
180 | },
181 | {
182 | x: Math.round(containerBounds.x),
183 | y: Math.round(containerBounds.y),
184 | width: Math.round(containerBounds.width),
185 | height: Math.round(containerBounds.height),
186 | }
187 | );
188 | })
189 | .then(done, done);
190 | });
191 |
192 | it('should update cache when children disappear', function (done) {
193 | const {vm, container, fake} = buildVm();
194 | Vue.nextTick()
195 | .then(() => {
196 | container.cacheNeedsUpdate = false;
197 | vm.showFake = false;
198 | return Vue.nextTick();
199 | })
200 | .then(() => {
201 | assert(container.cacheNeedsUpdate);
202 | })
203 | .then(done, done);
204 | });
205 |
206 | it('should update cache when children reappear', function (done) {
207 | const {vm, container, fake} = buildVm();
208 | Vue.nextTick()
209 | .then(() => {
210 | vm.showFake = false;
211 | return Vue.nextTick();
212 | })
213 | .then(() => {
214 | container.cacheNeedsUpdate = false;
215 | vm.showFake = true;
216 | return Vue.nextTick();
217 | })
218 | .then(() => {
219 | assert(container.cacheNeedsUpdate);
220 | })
221 | .then(done, done);
222 | });
223 | });
224 |
--------------------------------------------------------------------------------
/test/EaselDisplayObject.spec.js:
--------------------------------------------------------------------------------
1 | import EaselFake from './fixtures/EaselFake.js';
2 | import isADisplayObject from './includes/is-a-display-object.js';
3 |
4 | describe('EaselDisplayObject', function () {
5 |
6 | describe('is a display object that', isADisplayObject(EaselFake));
7 | });
8 |
--------------------------------------------------------------------------------
/test/EaselSprite.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import EaselSprite from '../src/components/EaselSprite.vue';
4 | import easeljs from '../easeljs/easel.js';
5 | import isADisplayObject from './includes/is-a-display-object.js';
6 | import canCache from './includes/can-cache.js';
7 | import isAlignable from './includes/is-alignable.js';
8 | import canFilter from './includes/can-filter.js';
9 |
10 | const garyStart = 32 * 6 + 16;
11 |
12 | const spriteSheet = new easeljs.SpriteSheet({
13 | images: ['/base/test/images/lastguardian-all.png'],
14 | frames: {width: 32, height: 32},
15 | animations: {
16 | stand: garyStart + 5,
17 | run: [garyStart + 6, garyStart + 7],
18 | },
19 | framerate: 30,
20 | });
21 |
22 | describe('EaselSprite', function () {
23 |
24 | describe('is a display object that', isADisplayObject(EaselSprite, '', {spriteSheet}));
25 |
26 | describe('is cacheable and', canCache(EaselSprite, {spriteSheet}, [
27 | {
28 | name: 'animation',
29 | value: 'stand',
30 | changeTo: 'run',
31 | shouldUpdateSameObject: true,
32 | },
33 | ]));
34 |
35 | describe('can filter and', canFilter(EaselSprite, '', {spriteSheet}));
36 |
37 | describe('is alignable and', isAlignable(EaselSprite, {width: 32, height: 32}, '', {spriteSheet}));
38 |
39 | const buildVm = function () {
40 | const easel = {
41 | addChild(vueChild) {
42 | },
43 | removeChild(vueChild) {
44 | },
45 | };
46 |
47 | const vm = new Vue({
48 | template: `
49 |
50 |
58 |
59 |
60 | `,
61 | provide() {
62 | return {
63 | spriteSheet,
64 | easelParent: easel,
65 | easelCanvas: easel,
66 | };
67 | },
68 | data() {
69 | return {
70 | animation: 'stand',
71 | x: 1,
72 | y: 2,
73 | showSprite: true,
74 | flip: '',
75 | align: 'top-left',
76 | };
77 | },
78 | components: {
79 | 'easel-sprite': EaselSprite,
80 | },
81 | }).$mount();
82 |
83 | const sprite = vm.$refs.sprite;
84 |
85 | return {vm, sprite};
86 | };
87 |
88 | it('should exist', function () {
89 | const {vm, sprite} = buildVm();
90 | assert(sprite);
91 | });
92 |
93 | it('should have a spritesheet', function () {
94 | const {vm, sprite} = buildVm();
95 | assert(sprite.spriteSheet);
96 | });
97 |
98 | it('should have component field', function () {
99 | const {vm, sprite} = buildVm();
100 | assert(sprite.component);
101 | });
102 |
103 | it('should run `stand` animation', function () {
104 | const {vm, sprite} = buildVm();
105 | assert(sprite.component._animation && sprite.component._animation.name === 'stand');
106 | });
107 |
108 | it('should change animation to `run`', function (done) {
109 | const {vm, sprite} = buildVm();
110 | vm.animation = 'run';
111 | Vue.nextTick()
112 | .then(() => {
113 | assert(sprite.component._animation && sprite.component._animation.name === 'run');
114 | })
115 | .then(done, done);
116 | });
117 |
118 | it('should get dimensions', function (done) {
119 | const {vm, sprite} = buildVm();
120 | sprite.getAlignDimensions()
121 | .then(dimensions => {
122 | assert(dimensions.width === 32);
123 | assert(dimensions.height === 32);
124 | })
125 | .then(done, done);
126 | });
127 |
128 | ['center-left', 'top-left', 'bottom-right']
129 | .forEach(align => {
130 | it('should get cache bounds (no matter the align)', function (done) {
131 | const {vm, sprite} = buildVm();
132 | vm.align = align;
133 | Vue.nextTick()
134 | .then(() => sprite.getCacheBounds())
135 | .then(({x, y, width, height}) => {
136 | assert(x === 0, `x is wrong: ${x}`);
137 | assert(y === 0, `y is wrong: ${y}`);
138 | assert(width === 32, `width is wrong: ${width}`);
139 | assert(height === 32, `height is wrong: ${height}`);
140 | })
141 | .then(done, done);
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/test/EaselSpriteSheet.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import EaselSpriteSheet from '../src/components/EaselSpriteSheet.vue';
4 | import easeljs from '../easeljs/easel.js';
5 |
6 | describe('EaselSpriteSheet', function () {
7 |
8 | const buildVm = function () {
9 | const vm = new Vue({
10 | template: `
11 |
17 |
18 |
19 | `,
20 | components: {
21 | 'easel-sprite-sheet': EaselSpriteSheet,
22 | 'x-inject': {
23 | inject: ['spriteSheet'],
24 | render() { return '' },
25 | },
26 | },
27 | }).$mount();
28 |
29 | const spriteSheet = vm.$refs.spriteSheet;
30 | const xInject = vm.$refs.xInject;
31 |
32 | return {vm, spriteSheet, xInject};
33 | };
34 |
35 | it('renders', function () {
36 | const {vm, spriteSheet, xInject} = buildVm();
37 | assert(spriteSheet);
38 | });
39 |
40 | it('should have spriteSheet field', function () {
41 | const {vm, spriteSheet, xInject} = buildVm();
42 | assert(spriteSheet.spriteSheet instanceof easeljs.SpriteSheet);
43 | });
44 |
45 | it('should have images in the spriteSheet', function () {
46 | const {vm, spriteSheet, xInject} = buildVm();
47 | assert(spriteSheet.spriteSheet._images);
48 | });
49 |
50 | it('should have frames in the spriteSheet', function () {
51 | const {vm, spriteSheet, xInject} = buildVm();
52 | assert(spriteSheet.spriteSheet._frameHeight === 32);
53 | assert(spriteSheet.spriteSheet._frameWidth === 32);
54 | });
55 |
56 | it('should have animations in the spriteSheet', function () {
57 | const {vm, spriteSheet, xInject} = buildVm();
58 | assert(spriteSheet.spriteSheet._animations.length > 0);
59 | });
60 |
61 | it('should have framerate in the spriteSheet', function () {
62 | const {vm, spriteSheet, xInject} = buildVm();
63 | assert(spriteSheet.spriteSheet.framerate === 30);
64 | });
65 |
66 | it('should provide spriteSheet', function () {
67 | const {vm, spriteSheet, xInject} = buildVm();
68 | assert(xInject.spriteSheet);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/EaselText.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import EaselText from '../src/components/EaselText.vue';
4 | import easeljs from '../easeljs/easel.js';
5 | import isADisplayObject from './includes/is-a-display-object.js';
6 | import canCache from './includes/can-cache.js';
7 | import canFilter from './includes/can-filter.js';
8 |
9 | describe('EaselText', function () {
10 |
11 | describe('is a display object that', isADisplayObject(EaselText, 'text="O hai"'));
12 |
13 | // EaselText is also alignable, but it uses special alignment rules, so it
14 | // doesn't include the alignment tests.
15 |
16 | describe('is cacheable and', canCache(EaselText, {}, [
17 | {
18 | name: 'color',
19 | value: 'black',
20 | changeTo: 'blue',
21 | shouldUpdateSameObject: true,
22 | },
23 | {
24 | name: 'text',
25 | value: 'Oh, hi',
26 | changeTo: 'Ohai',
27 | shouldUpdateSameObject: true,
28 | },
29 | {
30 | name: 'font',
31 | value: '12px "Times New Roman"',
32 | changeTo: '50px "Comic Sans"',
33 | shouldUpdateSameObject: true,
34 | },
35 | ]));
36 |
37 | describe('can filter and', canFilter(EaselText, 'text="O hai"'));
38 |
39 | const buildVm = function () {
40 | const easel = {
41 | addChild(vueChild) {
42 | },
43 | removeChild(vueChild) {
44 | },
45 | };
46 |
47 | const vm = new Vue({
48 | template: `
49 |
50 |
59 |
60 |
61 | `,
62 | provide() {
63 | return {
64 | easelParent: easel,
65 | easelCanvas: easel,
66 | };
67 | },
68 | data() {
69 | return {
70 | text: 'The Ran in Span Stays Manly On The Plan',
71 | showText: true,
72 | font: '20px Arial',
73 | color: 'black',
74 | align: ['left', 'top'],
75 | };
76 | },
77 | components: {
78 | 'easel-text': EaselText,
79 | },
80 | }).$mount();
81 |
82 | const text = vm.$refs.text;
83 |
84 | return {vm, text};
85 | };
86 |
87 | it('should exist', function () {
88 | const {vm, text} = buildVm();
89 | assert(text);
90 | });
91 |
92 | it('should have component field', function () {
93 | const {vm, text} = buildVm();
94 | assert(text.component);
95 | });
96 |
97 | it('should have the right text', function () {
98 | const {vm, text} = buildVm();
99 | assert(vm.text === text.component.text, 'Wrong text: ' + text.component.text);
100 | });
101 |
102 | it('should be able to change the text', function (done) {
103 | const {vm, text} = buildVm();
104 | vm.text = Math.random();
105 | Vue.nextTick()
106 | .then(() => {
107 | assert(vm.text === text.component.text, 'Wrong text in: ' + text.component.text);
108 | })
109 | .then(done, done);
110 | });
111 |
112 | it('should have the right font', function () {
113 | const {vm, text} = buildVm();
114 | assert(vm.font === text.component.font, 'Wrong font: ' + text.component.font);
115 | });
116 |
117 | it('should be able to change the font', function (done) {
118 | const {vm, text} = buildVm();
119 | vm.font = Math.random();
120 | Vue.nextTick()
121 | .then(() => {
122 | assert(vm.font === text.component.font, 'Wrong font in: ' + text.component.font);
123 | })
124 | .then(done, done);
125 | });
126 |
127 | it('should have the right color', function () {
128 | const {vm, text} = buildVm();
129 | assert(vm.color === text.component.color, 'Wrong color: ' + text.component.color);
130 | });
131 |
132 | it('should be able to change the color', function (done) {
133 | const {vm, text} = buildVm();
134 | vm.color = 'grey';
135 | Vue.nextTick()
136 | .then(() => {
137 | assert(vm.color === text.component.color, 'Wrong color in: ' + text.component.color);
138 | })
139 | .then(done, done);
140 | });
141 |
142 | it('should have the right align', function () {
143 | const {vm, text} = buildVm();
144 | assert('left' === text.component.textAlign, 'Wrong textAlign: ' + text.component.textAlign);
145 | assert('top' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
146 | });
147 |
148 | it('should be able to change the align', function (done) {
149 | const {vm, text} = buildVm();
150 | Vue.nextTick()
151 | .then(() => {
152 | vm.align = ['center', 'middle'];
153 | return Vue.nextTick();
154 | })
155 | .then(() => {
156 | assert('center' === text.component.textAlign, 'Wrong textAlign in: ' + text.component.textAlign);
157 | assert('middle' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
158 | })
159 | .then(done, done);
160 | });
161 |
162 | it('should default align correctly', function (done) {
163 | const {vm, text} = buildVm();
164 | Vue.nextTick()
165 | .then(() => {
166 | vm.align = ['', ''];
167 | return Vue.nextTick();
168 | })
169 | .then(() => {
170 | assert('top' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
171 | assert('left' === text.component.textAlign, 'Wrong default textAlign in: ' + text.component.textAlign);
172 | })
173 | .then(done, done);
174 | });
175 |
176 | it('should normalize reversed array align prop', function (done) {
177 | const {vm, text} = buildVm();
178 | Vue.nextTick()
179 | .then(() => {
180 | vm.align = ['bottom', 'right'];
181 | return Vue.nextTick();
182 | })
183 | .then(() => {
184 | assert('right' === text.component.textAlign, 'Wrong default textAlign in: ' + text.component.textAlign);
185 | assert('bottom' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
186 | })
187 | .then(done, done);
188 | });
189 |
190 | it('should normalize reversed string align prop', function (done) {
191 | const {vm, text} = buildVm();
192 | Vue.nextTick()
193 | .then(() => {
194 | vm.align = 'bottom-right';
195 | return Vue.nextTick();
196 | })
197 | .then(() => {
198 | assert('right' === text.component.textAlign, 'Wrong default textAlign in: ' + text.component.textAlign);
199 | assert('bottom' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
200 | })
201 | .then(done, done);
202 | });
203 |
204 | it('should convert center vertical to middle', function (done) {
205 | const {vm, text} = buildVm();
206 | Vue.nextTick()
207 | .then(() => {
208 | vm.align = 'center-center';
209 | return Vue.nextTick();
210 | })
211 | .then(() => {
212 | assert('center' === text.component.textAlign, 'Wrong default textAlign in: ' + text.component.textAlign);
213 | assert('middle' === text.component.textBaseline, 'Wrong default textBaseline in: ' + text.component.textBaseline);
214 | })
215 | .then(done, done);
216 | });
217 |
218 |
219 | it('should get cache bounds center-left', function (done) {
220 | const {vm, text} = buildVm();
221 | vm.align = 'center-left';
222 | Vue.nextTick()
223 | .then(() => text.getCacheBounds())
224 | .then(({x, y, width, height}) => {
225 | assert(x === 0, `x is wrong: ${x}`);
226 | assert(Math.floor(y) === -8, `y is wrong: ${y}`);
227 | assert(Math.floor(width) === 381, `width is wrong: ${width}`);
228 | assert(Math.floor(height) === 19, `height is wrong: ${height}`);
229 | })
230 | .then(done, done);
231 | });
232 |
233 | it('should get cache bounds top-left', function (done) {
234 | const {vm, text} = buildVm();
235 | vm.align = 'top-left';
236 | Vue.nextTick()
237 | .then(() => text.getCacheBounds())
238 | .then(({x, y, width, height}) => {
239 | assert(x === 0, `x is wrong: ${x}`);
240 | assert(y === 0, `y is wrong: ${y}`);
241 | assert(Math.floor(width) === 381, `width is wrong: ${width}`);
242 | assert(Math.floor(height) === 19, `height is wrong: ${height}`);
243 | })
244 | .then(done, done);
245 | });
246 |
247 | it('should get cache bounds bottom-right', function (done) {
248 | const {vm, text} = buildVm();
249 | vm.align = 'bottom-right';
250 | Vue.nextTick()
251 | .then(() => text.getCacheBounds())
252 | .then(({x, y, width, height}) => {
253 | assert(Math.floor(x) === -382, `x is wrong: ${x}`);
254 | assert(Math.floor(y) === -20, `y is wrong: ${y}`);
255 | assert(Math.floor(width) === 381, `width is wrong: ${width}`);
256 | assert(Math.floor(height) === 19, `height is wrong: ${height}`);
257 | })
258 | .then(done, done);
259 | });
260 |
261 | });
262 |
--------------------------------------------------------------------------------
/test/Events.spec.js:
--------------------------------------------------------------------------------
1 | import Events from '../src/libs/Events.js';
2 | import assert from 'assert';
3 | const {
4 | deepStrictEqual: equal,
5 | notDeepStrictEqual: notEqual,
6 | } = assert;
7 |
8 |
9 | describe('Events', function () {
10 |
11 | it('exists', function () {
12 | new Events();
13 | });
14 |
15 | it('lets us know when fired', function () {
16 | const events = new Events();
17 | let add = false;
18 | events.on('add', (param) => add = param);
19 | events.fire('add', '1234');
20 | equal('1234', add);
21 | });
22 |
23 | it('fires both cb even if one fails', function () {
24 | const events = new Events();
25 | let ran;
26 | events.on('add', () => { throw new Error() });
27 | events.on('add', () => ran = true);
28 | events.fire('add');
29 | assert(ran);
30 | });
31 |
32 | it('fires an error if an error happens', function () {
33 | const events = new Events({errorCode: 'error'});
34 | let errored;
35 | events.on('add', () => { throw new Error() });
36 | events.on('error', () => errored = true);
37 | events.fire('add');
38 | assert(errored);
39 | });
40 |
41 | it('does not fire an error if an error happens while firing an error', function () {
42 | const events = new Events({errorCode: 'error'});
43 | events.on('error', () => { throw new Error() });
44 | events.fire('error');
45 | // no infinite recursion
46 | });
47 |
48 | it('turn off', function () {
49 | const events = new Events();
50 | let add = false;
51 | const cb = (param) => add = param;
52 | events.on('add', cb);
53 | events.fire('add', '1234');
54 | equal('1234', add);
55 | events.off('add', cb);
56 | events.fire('add', 'nobody to listen');
57 | equal('1234', add);
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/test/FilterSet.spec.js:
--------------------------------------------------------------------------------
1 | import FilterSet from '../src/filters/FilterSet.js';
2 | import assert from 'assert';
3 | const {deepStrictEqual: equal} = assert;
4 |
5 | const randomName = () => 'Custom' + new String(Math.random()).substr(-8);
6 |
7 | describe('FilterSet', function () {
8 |
9 | it('registers and builds a class with applyFilter', function () {
10 | const name = randomName();
11 | let caughtArguments;
12 | const filters = new FilterSet();
13 | filters.register(name, class Custom {
14 | constructor(x) {
15 | this.x = x;
16 | }
17 | applyFilter(...args) {
18 | caughtArguments = args;
19 | return true;
20 | }
21 | });
22 | const filter = filters.build([name, 'y']);
23 | assert(typeof filter.applyFilter === 'function');
24 | assert(filter.applyFilter(1, 2, 3, 4, 5, 6, 7, 8));
25 | equal([1, 2, 3, 4, 5, 6, 7, 8], caughtArguments);
26 | assert(filter.x === 'y');
27 | });
28 |
29 | it('registers and builds a class with adjustImageData', function () {
30 | const name = randomName();
31 | let caughtArguments;
32 | const filters = new FilterSet();
33 | filters.register(name, class Custom {
34 | constructor(x) {
35 | this.x = x;
36 | }
37 | adjustImageData(...args) {
38 | caughtArguments = args;
39 | return true;
40 | }
41 | });
42 | const ctx = {
43 | getImageData(){ return 'image data'; },
44 | putImageData(){ },
45 | };
46 | const filter = filters.build([name, 'y']);
47 | assert(typeof filter.applyFilter === 'function');
48 | assert(filter.applyFilter(ctx, 2, 3, 4, 5, ctx, 7, 8));
49 | equal(['image data'], caughtArguments);
50 | assert(filter.x === 'y');
51 | });
52 |
53 | it('registers and builds a class with adjustContext', function () {
54 | const name = randomName();
55 | let caughtArguments;
56 | const filters = new FilterSet();
57 | filters.register(name, class Custom {
58 | constructor(x) {
59 | this.x = x;
60 | }
61 | adjustContext(...args) {
62 | caughtArguments = args;
63 | return true;
64 | }
65 | });
66 | const filter = filters.build([name, 'y']);
67 | assert(typeof filter.applyFilter === 'function');
68 | assert(filter.applyFilter(1, 2, 3, 4, 5, 6, 7, 8));
69 | equal([1, 2, 3, 4, 5, 6, 7, 8], caughtArguments);
70 | assert(filter.x === 'y');
71 | });
72 |
73 | it('rejects a class with none of those', function () {
74 | let caught;
75 | const filters = new FilterSet();
76 | try {
77 | filters.register(randomName(), class Custom {
78 | });
79 | } catch (e) {
80 | caught = e;
81 | }
82 | assert(caught);
83 | });
84 |
85 | it('rejects a class with just _applyFilter', function () {
86 | let caught;
87 | const filters = new FilterSet();
88 | try {
89 | filters.register(randomName(), class Custom {
90 | _applyFilter(){}
91 | });
92 | } catch (e) {
93 | caught = e;
94 | }
95 | assert(caught);
96 | });
97 |
98 | it('fails to build an unknown class', function () {
99 | let caught;
100 | const filters = new FilterSet();
101 | try {
102 | filters.build(['NO_SUCH_NAME']);
103 | } catch (e) {
104 | caught = e;
105 | }
106 | assert(caught);
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/test/PixelStrokeFilter.spec.js:
--------------------------------------------------------------------------------
1 | import PixelStrokeFilter from '../src/filters/PixelStrokeFilter.js';
2 | import easeljs from '../easeljs/easel.js';
3 | import assert from 'assert';
4 | const {deepStrictEqual: equal} = assert;
5 |
6 | const stringify = (data) => {
7 | return data.map((n, i) => ('0' + n.toString(16)).substr(-2) + (i % 4 === 3 ? ' ' : '')).join('');
8 | };
9 |
10 | describe('PixelStrokeFilter', function () {
11 |
12 | it('instantiates', function () {
13 | new PixelStrokeFilter([], 1, {antiAlias: false});
14 | });
15 |
16 | it('draws a stroke around a tic-tac-toe center', function () {
17 | const imageData = {
18 | data: [
19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
20 | 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0,
21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22 | ],
23 | width: 3,
24 | height: 3,
25 | };
26 | new PixelStrokeFilter([], 1, {antiAlias: false}).adjustImageData(imageData);
27 | equal(
28 | stringify([
29 | 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,
30 | 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255,
31 | 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,
32 | ]),
33 | stringify(imageData.data)
34 | );
35 | });
36 |
37 | it('draws a stroke around a tic-tac-toe bottom-right corner', function () {
38 | const imageData = {
39 | data: [
40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42 | 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255,
43 | ],
44 | width: 3,
45 | height: 3,
46 | };
47 | new PixelStrokeFilter([], 1, {antiAlias: false}).adjustImageData(imageData);
48 | equal(
49 | stringify([
50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
51 | 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255,
52 | 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255,
53 | ]),
54 | stringify(imageData.data)
55 | );
56 | });
57 |
58 | it('draws a stroke around a tic-tac-toe bottom-right corner', function () {
59 | const imageData = {
60 | data: [
61 | 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0,
62 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
63 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
64 | ],
65 | width: 3,
66 | height: 3,
67 | };
68 | new PixelStrokeFilter([], 1, {antiAlias: false}).adjustImageData(imageData);
69 | equal(
70 | stringify([
71 | 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 0,
72 | 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0,
73 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74 | ]),
75 | stringify(imageData.data)
76 | );
77 | });
78 |
79 | it('draws a colored stroke around a colored tic-tac-toe center', function () {
80 | const rand255 = () => Math.floor(Math.random() * 255);
81 | const sr = rand255();
82 | const sg = rand255();
83 | const sb = rand255();
84 | const sa = rand255() || 255;
85 | const pr = rand255();
86 | const pg = rand255();
87 | const pb = rand255();
88 | const pa = rand255() || 255;
89 | const imageData = {
90 | data: [
91 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
92 | 0, 0, 0, 0, pr, pg, pb, pa, 0, 0, 0, 0,
93 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
94 | ],
95 | width: 3,
96 | height: 3,
97 | };
98 | new PixelStrokeFilter([sr, sg, sb, sa], 1, {antiAlias: false}).adjustImageData(imageData);
99 | equal(
100 | stringify([
101 | sr, sg, sb, sa, sr, sg, sb, sa, sr, sg, sb, sa,
102 | sr, sg, sb, sa, pr, pg, pb, pa, sr, sg, sb, sa,
103 | sr, sg, sb, sa, sr, sg, sb, sa, sr, sg, sb, sa,
104 | ]),
105 | stringify(imageData.data)
106 | );
107 | });
108 |
109 | it('calculates a size 1 circle', function () {
110 | const filter = new PixelStrokeFilter([0, 0, 0, 1], 1);
111 | const expected = {
112 | '1': {y: 1, minX: -1, maxX: 1, a: Math.sin(Math.PI / 4)},
113 | '0': {y: 0, minX: -1, maxX: 1, a: 1},
114 | '-1': {y: -1, minX: -1, maxX: 1, a: Math.sin(Math.PI / 4)},
115 | };
116 | equal(Object.keys(expected).length, filter.brush.length, JSON.stringify(filter.brush));
117 | filter.brush.forEach(point => {
118 | equal(expected[point.y], point, JSON.stringify(filter.brush));
119 | });
120 | });
121 |
122 | it('calculates a size 2 circle', function () {
123 | const filter = new PixelStrokeFilter([0, 0, 0, 1], 2);
124 | const expected = {
125 | '-2': {"y":-2, "minX":-2, "maxX":2, "a":0.41421356237309503},
126 | '-1': {"y":-1, "minX":-2, "maxX":2, "a":0.8416407864998738},
127 | '0': {"y":0, "minX":-2, "maxX":2, "a":1},
128 | '1': {"y":1, "minX":-2, "maxX":2, "a":0.8416407864998738},
129 | '2': {"y":2, "minX":-2, "maxX":2, "a":0.41421356237309503},
130 | };
131 | equal(Object.keys(expected).length, filter.brush.length, JSON.stringify(filter.brush));
132 | filter.brush.forEach(point => {
133 | equal(expected[point.y], point, JSON.stringify(filter.brush));
134 | });
135 | });
136 |
137 | it('calculates a size 20 circle', function () {
138 | const filter = new PixelStrokeFilter([0, 0, 0, 1], 20);
139 | const expected = {
140 | '-20': {"y":-20,"minX":0,"maxX":0,"a":1},
141 | '-19': {"y":-19,"minX":-7,"maxX":7,"a":0.7},
142 | '-18': {"y":-18,"minX":-9,"maxX":9,"a":0.9},
143 | '-17': {"y":-17,"minX":-11,"maxX":11,"a":0.8},
144 | '-16': {"y":-16,"minX":-12,"maxX":12,"a":1},
145 | '-15': {"y":-15,"minX":-15,"maxX":15,"a":0.1},
146 | '-14': {"y":-14,"minX":-15,"maxX":15,"a":0.6},
147 | '-13': {"y":-13,"minX":-16,"maxX":16,"a":0.6},
148 | '-12': {"y":-12,"minX":-16,"maxX":16,"a":1},
149 | '-11': {"y":-11,"minX":-17,"maxX":17,"a":0.8},
150 | '-10': {"y":-10,"minX":-18,"maxX":18,"a":0.6},
151 | '-9': {"y":-9,"minX":-18,"maxX":18,"a":0.9},
152 | '-8': {"y":-8,"minX":-19,"maxX":19,"a":0.5},
153 | '-7': {"y":-7,"minX":-19,"maxX":19,"a":0.7},
154 | '-6': {"y":-6,"minX":-20,"maxX":20,"a":0.5},
155 | '-5': {"y":-5,"minX":-20,"maxX":20,"a":0.6},
156 | '-4': {"y":-4,"minX":-20,"maxX":20,"a":0.8},
157 | '-3': {"y":-3,"minX":-20,"maxX":20,"a":0.9},
158 | '-2': {"y":-2,"minX":-20,"maxX":20,"a":0.9},
159 | '-1': {"y":-1,"minX":-20,"maxX":20,"a":1},
160 | '0': {"y":0,"minX":-20,"maxX":20,"a":1},
161 | '1': {"y":1,"minX":-20,"maxX":20,"a":1},
162 | '2': {"y":2,"minX":-20,"maxX":20,"a":0.9},
163 | '3': {"y":3,"minX":-20,"maxX":20,"a":0.9},
164 | '4': {"y":4,"minX":-20,"maxX":20,"a":0.8},
165 | '5': {"y":5,"minX":-20,"maxX":20,"a":0.6},
166 | '6': {"y":6,"minX":-20,"maxX":20,"a":0.5},
167 | '7': {"y":7,"minX":-19,"maxX":19,"a":0.7},
168 | '8': {"y":8,"minX":-19,"maxX":19,"a":0.5},
169 | '9': {"y":9,"minX":-18,"maxX":18,"a":0.9},
170 | '10': {"y":10,"minX":-18,"maxX":18,"a":0.6},
171 | '11': {"y":11,"minX":-17,"maxX":17,"a":0.8},
172 | '12': {"y":12,"minX":-16,"maxX":16,"a":1},
173 | '13': {"y":13,"minX":-16,"maxX":16,"a":0.6},
174 | '14': {"y":14,"minX":-15,"maxX":15,"a":0.6},
175 | '15': {"y":15,"minX":-15,"maxX":15,"a":0.1},
176 | '16': {"y":16,"minX":-12,"maxX":12,"a":1},
177 | '17': {"y":17,"minX":-11,"maxX":11,"a":0.8},
178 | '18': {"y":18,"minX":-9,"maxX":9,"a":0.9},
179 | '19': {"y":19,"minX":-7,"maxX":7,"a":0.7},
180 | '20': {"y":20,"minX":0,"maxX":0,"a":1},
181 | };
182 | equal(Object.keys(expected).length, filter.brush.length, JSON.stringify(filter.brush));
183 | filter.brush.map(({y, minX, maxX, a}) => { return {y, minX, maxX, a: Math.round(a * 10) / 10}; })
184 | .forEach(point => {
185 | equal(expected[point.y], point, JSON.stringify({expected: expected[point.y], actual: point}));
186 | });
187 | });
188 |
189 | it('expands a rectangle to include radius of brush', function () {
190 | const filter = new PixelStrokeFilter([0, 0, 0, 1], 20);
191 | const sourceRect = new easeljs.Rectangle(0, 0, 100, 100);
192 | const rect = filter.getBounds(sourceRect);
193 | equal(
194 | new easeljs.Rectangle(-40, -40, 180, 180),
195 | rect
196 | );
197 | assert(sourceRect === rect);
198 | });
199 |
200 | it('creates a rectangle to include radius of brush', function () {
201 | const filter = new PixelStrokeFilter([0, 0, 0, 1], 20);
202 | const rect = filter.getBounds();
203 | equal(
204 | new easeljs.Rectangle(-40, -40, 80, 80),
205 | rect
206 | );
207 | });
208 |
209 | it('draws an anti-aliased stroke around a tic-tac-toe center', function () {
210 | const imageData = {
211 | data: [
212 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
213 | 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0,
214 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
215 | ],
216 | width: 3,
217 | height: 3,
218 | };
219 | new PixelStrokeFilter([], 1, {antiAlias: true}).adjustImageData(imageData);
220 | const cosine_of_45_degrees = .707;
221 | const alpha = Math.round(255 * cosine_of_45_degrees);
222 | equal(
223 | stringify([
224 | 0, 0, 0, alpha, 0, 0, 0, 255, 0, 0, 0, alpha,
225 | 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255,
226 | 0, 0, 0, alpha, 0, 0, 0, 255, 0, 0, 0, alpha,
227 | ]),
228 | stringify(imageData.data)
229 | );
230 | });
231 |
232 | it('draws an anti-aliased stroke around a half-alpha tic-tac-toe center', function () {
233 | const imageData = {
234 | data: [
235 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
236 | 0, 0, 0, 0, 255, 255, 255, 127, 0, 0, 0, 0,
237 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
238 | ],
239 | width: 3,
240 | height: 3,
241 | };
242 | new PixelStrokeFilter([], 1, {antiAlias: true}).adjustImageData(imageData);
243 | const cosine_of_45_degrees = .707;
244 | const alpha = Math.round(255 * cosine_of_45_degrees);
245 | equal(
246 | stringify([
247 | 0, 0, 0, alpha, 0, 0, 0, 255, 0, 0, 0, alpha,
248 | 0, 0, 0, 255, 255, 255, 255, 127, 0, 0, 0, 255,
249 | 0, 0, 0, alpha, 0, 0, 0, 255, 0, 0, 0, alpha,
250 | ]),
251 | stringify(imageData.data)
252 | );
253 | });
254 | });
255 |
--------------------------------------------------------------------------------
/test/PromiseParty.spec.js:
--------------------------------------------------------------------------------
1 | import PromiseParty from '../src/libs/PromiseParty.js';
2 | import assert from 'assert';
3 | const {
4 | deepStrictEqual: equal,
5 | notDeepStrictEqual: notEqual,
6 | } = assert;
7 |
8 |
9 | describe('PromiseParty', function () {
10 |
11 | it('exists', function () {
12 | new PromiseParty();
13 | });
14 |
15 | it('adds a Promise', function () {
16 | const party = new PromiseParty();
17 | party.add(Promise.resolve());
18 | });
19 |
20 | it('does not add a not-Promise', function () {
21 | const party = new PromiseParty();
22 | let error;
23 | try {
24 | party.add({});
25 | } catch (e) {
26 | error = e;
27 | }
28 | assert(error);
29 | });
30 |
31 | it('lets us know when a Promise is added', function () {
32 | const party = new PromiseParty();
33 | let add = false;
34 | party.on('add', () => add = true);
35 | party.add(Promise.resolve());
36 | assert(add);
37 | });
38 |
39 | it('lets us know when a Promise completes', function (done) {
40 | const party = new PromiseParty();
41 | let remove = false;
42 | party.on('remove', () => remove = true);
43 | let resolve;
44 | const promise = new Promise(r => resolve = r);
45 | party.add(promise);
46 | resolve();
47 | promise
48 | .then(() => assert(remove))
49 | .then(done, done);
50 | });
51 |
52 | it('lets us know how many promises there are', function (done) {
53 | const party = new PromiseParty();
54 | let lastAdded;
55 | let lastRemoved;
56 | let lastChanged;
57 | party.on('add', (count) => lastAdded = count);
58 | party.on('remove', (count) => lastRemoved = count);
59 | party.on('change', (count) => lastChanged = count);
60 | const promise1 = Promise.resolve();
61 | let resolve2;
62 | const promise2 = new Promise(r => resolve2 = r);
63 | let reject3;
64 | const promise3 = new Promise((z, r) => reject3 = r);
65 | party.add(promise1);
66 | equal(1, lastAdded);
67 | equal(1, lastChanged);
68 | promise1
69 | .then(() => equal(0, lastRemoved))
70 | .then(() => equal(0, lastChanged))
71 | .then(() => {
72 | party.add(promise2);
73 | equal(1, lastAdded); // because of promise2
74 | equal(1, lastChanged);
75 | party.add(promise3);
76 | equal(2, lastAdded); // because of promise2 and promise3
77 | equal(2, lastChanged);
78 | equal(0, lastRemoved); // no change
79 | })
80 | .then(() => resolve2())
81 | .then(() => promise2)
82 | .then(() => {
83 | equal(2, lastAdded); // no change
84 | equal(1, lastRemoved); // because promise2 ended
85 | equal(1, lastChanged);
86 | })
87 | .then(() => reject3())
88 | .then(() => promise3)
89 | .then(() => {
90 | equal(2, lastAdded); // no change
91 | equal(0, lastRemoved); // because promise3 ended
92 | equal(0, lastChanged);
93 | })
94 | .then(done, done);
95 | });
96 |
97 | it('returns itself when adding and removing listeners', function () {
98 | const party = new PromiseParty();
99 | const same = party.on('add', () => void(0));
100 | assert(party === same);
101 | const sameAgain = party.off('not even a thing', () => void(0));
102 | assert(party === sameAgain);
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/test/easel-event-binder.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import EaselEventBinder from '../src/libs/easel-event-binder.js';
3 |
4 | const eventTypes = ['added', 'click', 'dblclick', 'mousedown', 'mouseout', 'mouseover', 'pressmove', 'pressup', 'removed', 'rollout', 'rollover', 'tick', 'animationend', 'change'];
5 | const fauxParentListeners = {};
6 | eventTypes.forEach(type => {
7 | fauxParentListeners[type] = function () {};
8 | });
9 |
10 | describe('easel-event-binder.js', function () {
11 |
12 | it('should bind all the things', function () {
13 | const got = {};
14 | const vueComponent = {
15 | $options: {
16 | _parentListeners: fauxParentListeners,
17 | },
18 | $emit(eventType, event) {
19 | got[eventType] = event;
20 | },
21 | };
22 | const easelComponent = {
23 | addEventListener(event, handler) {
24 | handler({type: event});
25 | },
26 | };
27 | EaselEventBinder.bindEvents(
28 | vueComponent,
29 | easelComponent
30 | );
31 | eventTypes.forEach(eventType => {
32 | assert(got[eventType]);
33 | });
34 | });
35 |
36 | it('should only bind dblclick', function () {
37 | const got = {};
38 | const vueComponent = {
39 | $options: {
40 | _parentListeners: {
41 | dblclick() {},
42 | NOT_A_REAL_EVENT_TYPE() {},
43 | },
44 | },
45 | $emit(eventType, event) {
46 | got[eventType] = event;
47 | },
48 | };
49 | const easelComponent = {
50 | addEventListener(event, handler) {
51 | handler({type: event});
52 | },
53 | };
54 | EaselEventBinder.bindEvents(
55 | vueComponent,
56 | easelComponent
57 | );
58 | assert(Object.keys(got).length === 1, 'bound too many things');
59 | assert(got.dblclick);
60 | });
61 |
62 | it('should not bind', function () {
63 | const got = {};
64 | const vueComponent = {
65 | $options: {
66 | },
67 | $emit(eventType, event) {
68 | got[eventType] = event;
69 | },
70 | };
71 | const easelComponent = {
72 | addEventListener(event, handler) {
73 | handler({type: event});
74 | },
75 | };
76 | EaselEventBinder.bindEvents(
77 | vueComponent,
78 | easelComponent
79 | );
80 | assert(Object.keys(got).length === 0, 'bound too many things');
81 | });
82 |
83 | it('should augment an event via the canvas', function () {
84 | const event = {};
85 | const easelCanvas = {
86 | augmentEvent(event) {
87 | event.changed = true;
88 | return event;
89 | },
90 | };
91 | const vueComponent = {
92 | easelCanvas,
93 | $options: {
94 | _parentListeners: {
95 | click() {},
96 | },
97 | },
98 | $emit() {},
99 | };
100 | const easelComponent = {
101 | addEventListener(eventType, handler) {
102 | handler(event);
103 | },
104 | };
105 | EaselEventBinder.bindEvents(vueComponent, easelComponent);
106 | assert(event.changed);
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/test/fixtures/EaselFake.js:
--------------------------------------------------------------------------------
1 | import EaselDisplayObject from '../../src/mixins/EaselDisplayObject.js';
2 | import EaselCache from '../../src/mixins/EaselCache.js';
3 | import easeljs from '../../easeljs/easel.js';
4 |
5 | /**
6 | * A fake component that uses EaselDisplayObject.
7 | * It uses a generic Shape internally.
8 | * It has the size of a 32x48 rectangle.
9 | */
10 | export default {
11 | template: '',
12 | mixins: [EaselDisplayObject, EaselCache],
13 | mounted() {
14 | this.component = new easeljs.Shape();
15 | },
16 | methods: {
17 | getAlignDimensions() {
18 | return Promise.resolve({width: 32, height: 48});
19 | },
20 | getCacheBounds() {
21 | return Promise.resolve({x: -10, y: -20, width: 30, height: 40});
22 | },
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/test/fixtures/Set.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Cheap implementation of modern JS's Set class
3 | */
4 | export default class Set {
5 |
6 | constructor(array = []) {
7 | this.array = [...array];
8 | this.size = array.length;
9 | }
10 |
11 | has(element) {
12 | return this.array.indexOf(element) >= 0;
13 | }
14 |
15 | add(element) {
16 | if (!this.has(element)) {
17 | this.array.push(element);
18 | this.size = this.array.length;
19 | }
20 | }
21 |
22 | delete(element) {
23 | if (this.has(element)) {
24 | this.array.splice(this.array.indexOf(element), 1);
25 | this.size = this.array.length;
26 | }
27 | }
28 |
29 | // These should be easy to implement, but I haven't checked them against a
30 | // real implementation:
31 |
32 | // forEach(cb) {
33 | // this.array.forEach(cb);
34 | // }
35 |
36 | // keys() {
37 | // return this.values();
38 | // }
39 |
40 | // values() {
41 | // return [...this.array];
42 | // }
43 | };
44 |
--------------------------------------------------------------------------------
/test/images/gulfstream_park.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dankuck/vue-easeljs/ffcbcb667a0029c91de48e8a27fe4bdec7a8df4b/test/images/gulfstream_park.jpg
--------------------------------------------------------------------------------
/test/images/lastguardian-all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dankuck/vue-easeljs/ffcbcb667a0029c91de48e8a27fe4bdec7a8df4b/test/images/lastguardian-all.png
--------------------------------------------------------------------------------
/test/includes/can-cache.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import assert from 'assert';
3 |
4 | const wait = function (component, count = 1) {
5 | let promise = Promise.all([
6 | component.getCacheBounds(),
7 | Vue.nextTick(),
8 | ]);
9 | if (count > 1) {
10 | return promise.then(() => wait(component, count - 1));
11 | } else {
12 | return promise;
13 | }
14 | };
15 |
16 | const parentPropChangers = [
17 | {
18 | name: 'x',
19 | value: 0,
20 | changeTo: 1,
21 | shouldUpdateSameObject: false,
22 | },
23 | {
24 | name: 'y',
25 | value: 0,
26 | changeTo: 2,
27 | shouldUpdateSameObject: false,
28 | },
29 | {
30 | name: 'flip',
31 | value: '',
32 | changeTo: 'horizontal',
33 | shouldUpdateSameObject: false,
34 | },
35 | {
36 | name: 'rotation',
37 | value: '',
38 | changeTo: '90',
39 | shouldUpdateSameObject: false,
40 | },
41 | {
42 | name: 'scale',
43 | value: 1,
44 | changeTo: 2,
45 | shouldUpdateSameObject: true,
46 | },
47 | {
48 | name: 'alpha',
49 | value: 1,
50 | changeTo: .5,
51 | shouldUpdateSameObject: false,
52 | },
53 | {
54 | name: 'shadow',
55 | value: ['red', 5, 6, .5],
56 | changeTo: ['blue', 5, 6, .5],
57 | shouldUpdateSameObject: false,
58 | },
59 | // `align` could technically be on this list, but it's not guaranteed to be
60 | // on all EaselCache components
61 | ];
62 |
63 | export default function (implementor, provide = {}, propChangers = []) {
64 | return function () {
65 |
66 | const allPropChangers = propChangers
67 | .concat(parentPropChangers)
68 |
69 | const buildVm = function () {
70 | /**
71 | * A fake easel object
72 | */
73 | const easel = {
74 | addChild() {
75 | },
76 | removeChild() {
77 | },
78 | cacheNeedsUpdate: false,
79 | createCanvas(cb) {
80 | return cb();
81 | },
82 | };
83 |
84 | const container = new Vue({
85 | provide() {
86 | return {
87 | easelParent: easel,
88 | easelCanvas: easel,
89 | };
90 | },
91 | data() {
92 | return {
93 | addChild() {
94 | },
95 | removeChild() {
96 | },
97 | cacheNeedsUpdate: false,
98 | createCanvas(cb) {
99 | return cb();
100 | },
101 | scale: 1,
102 | };
103 | },
104 | });
105 |
106 | const props = allPropChangers
107 | .map(changer => changer.name)
108 | .map(name => `:${name}="${name}"`)
109 | .join("\n");
110 |
111 | const vm = new Vue({
112 | template: `
113 |
114 |
118 |
119 |
120 | `,
121 | provide() {
122 | provide.easelParent = container;
123 | provide.easelCanvas = easel;
124 | return provide;
125 | },
126 | data() {
127 | const data = {
128 | cache: true,
129 | };
130 | return allPropChangers
131 | .reduce((acc, changer) => {
132 | acc[changer.name] = changer.value;
133 | return acc;
134 | }, data);
135 | },
136 | components: {
137 | implementor,
138 | },
139 | methods: {
140 | },
141 | }).$mount();
142 |
143 | const fake = vm.$refs.fake;
144 |
145 | return {fake, vm, easel, container};
146 | };
147 |
148 | it('can cache on init', function (done) {
149 | const {vm, fake} = buildVm();
150 | assert(fake.cache === true);
151 | assert(fake.component.cacheCanvas === null);
152 | wait(fake)
153 | .then(() => {
154 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
155 | })
156 | .then(done, done);
157 | });
158 |
159 | it('can clear cache', function (done) {
160 | const {vm, fake} = buildVm();
161 | assert(fake.cache === true);
162 | wait(fake)
163 | .then(() => {
164 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
165 | vm.cache = false;
166 | return wait(fake, 4);
167 | })
168 | .then(() => {
169 | assert(fake.component.cacheCanvas === null, 'Did not destroy cache');
170 | })
171 | .then(done, done);
172 | });
173 |
174 | it('can clear and recreate cache', function (done) {
175 | const {vm, fake} = buildVm();
176 | assert(fake.cache === true);
177 | wait(fake)
178 | .then(() => {
179 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
180 | vm.cache = false;
181 | return wait(fake);
182 | })
183 | .then(() => {
184 | assert(fake.component.cacheCanvas === null, 'Did not destroy cache');
185 | vm.cache = true;
186 | return wait(fake, 2);
187 | })
188 | .then(() => {
189 | assert(fake.component.cacheCanvas !== null, 'Did not re-create cache');
190 | })
191 | .then(done, done);
192 | });
193 |
194 | // Search catchers:
195 | // ... should YES update cache when ...
196 | // ... should NOT update cache when ...
197 | allPropChangers
198 | .forEach(({name, changeTo, shouldUpdateSameObject}) => {
199 | it(`should ${shouldUpdateSameObject ? 'YES' : 'NOT'} update cache when ${name} changes`, function (done) {
200 | const {vm, fake} = buildVm();
201 | assert(fake.cache === true);
202 | let bitmapCache, cacheID;
203 | wait(fake)
204 | .then(() => {
205 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
206 | bitmapCache = fake.component.bitmapCache;
207 | cacheID = bitmapCache && bitmapCache.cacheID;
208 | vm[name] = changeTo;
209 | return wait(fake);
210 | })
211 | .then(() => {
212 | // Check the bitmapCache object and the cacheID
213 | // because the cache may have updated by replacing
214 | // the whole object or it may have just updated and
215 | // incremented its ID.
216 | const updated = (
217 | bitmapCache !== fake.component.bitmapCache
218 | || cacheID !== bitmapCache.cacheID
219 | );
220 | assert(updated === shouldUpdateSameObject, `${name} did ${updated ? 'YES' : 'NOT'} cause an update: ${fake[name]}`);
221 | })
222 | .then(done, done);
223 | });
224 | });
225 |
226 | // Search catchers:
227 | // ... should YES update easel.cacheNeedsUpdate when ...
228 | // ... should NOT update easel.cacheNeedsUpdate when ...
229 | allPropChangers
230 | .forEach(({name, changeTo}) => {
231 | it(`should update easel.cacheNeedsUpdate when ${name} changes`, function (done) {
232 | const {vm, fake, easel, container} = buildVm();
233 | // Works whether or not cache is on for this component
234 | vm.cache = Math.random() > .5 ? true : false;
235 | // Let the component catch up with `cache` change
236 | wait(fake)
237 | .then(() => {
238 | easel.cacheNeedsUpdate = false;
239 | vm[name] = changeTo;
240 | return wait(fake);
241 | })
242 | .then(() => {
243 | assert(container.cacheNeedsUpdate === true, `${name} did NOT cause an update: ${fake[name]}`);
244 | })
245 | .then(done, done);
246 | });
247 | });
248 |
249 | it('should update on change event', function (done) {
250 | const {vm, fake, easel, container} = buildVm();
251 | assert(fake.cache === true);
252 | let bitmapCache, cacheID;
253 | wait(fake, 2) // EaselBitmap needs an extra tick
254 | .then(() => {
255 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
256 | bitmapCache = fake.component.bitmapCache;
257 | cacheID = bitmapCache && bitmapCache.cacheID;
258 | easel.cacheNeedsUpdate = false;
259 | fake.component.dispatchEvent('change')
260 | return wait(fake);
261 | })
262 | .then(() => {
263 | // Check the bitmapCache object and the cacheID
264 | // because the cache may have updated by replacing
265 | // the whole object or it may have just updated and
266 | // incremented its ID.
267 | const updated = (
268 | bitmapCache !== fake.component.bitmapCache
269 | || cacheID !== bitmapCache.cacheID
270 | );
271 | assert(updated, `Event 'change' did not cause an update`);
272 | assert(container.cacheNeedsUpdate === true, `Event 'change' did NOT cause an update to container.cacheNeedsUpdate`);
273 | })
274 | .then(done, done);
275 | });
276 |
277 | it('should update cache on window resize', function (done) {
278 | const {vm, fake, easel, container} = buildVm();
279 | let bitmapCache, cacheID;
280 | wait(fake, 2) // EaselBitmap needs an extra tick
281 | .then(() => {
282 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
283 | bitmapCache = fake.component.bitmapCache;
284 | cacheID = bitmapCache && bitmapCache.cacheID;
285 | window.dispatchEvent(new Event('resize'));
286 | return wait(fake);
287 | })
288 | .then(() => {
289 | // Check the bitmapCache object and the cacheID
290 | // because the cache may have updated by replacing
291 | // the whole object or it may have just updated and
292 | // incremented its ID.
293 | const updated = (
294 | bitmapCache !== fake.component.bitmapCache
295 | || cacheID !== bitmapCache.cacheID
296 | );
297 | assert(updated, `Window resize did not cause an update`);
298 | assert(container.cacheNeedsUpdate === true, `Window resize did NOT cause an update to container.cacheNeedsUpdate`);
299 | })
300 | .then(done, done);
301 | });
302 |
303 | it('should update cache size on update', function (done) {
304 | const {vm, fake} = buildVm();
305 | assert(fake.cache === true);
306 | let width, height;
307 | wait(fake, 2)
308 | .then(() => {
309 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
310 | width = fake.component.cacheCanvas.width;
311 | height = fake.component.cacheCanvas.height;
312 | vm.scale = 10;
313 | return wait(fake, 2);
314 | })
315 | .then(() => {
316 | assert(fake.component.cacheCanvas.width > (width - 1) * 10);
317 | assert(fake.component.cacheCanvas.height > (height - 1) * 10);
318 | assert(fake.component.cacheCanvas.width < (width + 1) * 10);
319 | assert(fake.component.cacheCanvas.height < (height + 1) * 10);
320 | })
321 | .then(done, done);
322 | });
323 |
324 | it('runs beforeCache on cache init', function (done) {
325 | const {vm, fake} = buildVm();
326 | let ran = false;
327 | fake.beforeCache(() => ran = true);
328 | assert(fake.cache === true);
329 | assert(fake.component.cacheCanvas === null);
330 | wait(fake)
331 | .then(() => {
332 | assert(ran);
333 | })
334 | .then(done, done);
335 | });
336 |
337 | it('runs beforeCache on cache update', function (done) {
338 | const {vm, fake} = buildVm();
339 | let ran = false;
340 | assert(fake.cache === true);
341 | assert(fake.component.cacheCanvas === null);
342 | wait(fake, 2)
343 | .then(() => {
344 | assert(!ran);
345 | fake.beforeCache(() => ran = true);
346 | fake.cacheNeedsUpdate = true;
347 | return wait(fake, 2);
348 | })
349 | .then(() => {
350 | assert(ran);
351 | })
352 | .then(done, done);
353 | });
354 |
355 | it('caches based on cacheWhen()', function (done) {
356 | const {vm, fake} = buildVm();
357 | wait(fake, 2)
358 | .then(() => {
359 | vm.cache = false;
360 | return wait(fake, 2);
361 | })
362 | .then(() => {
363 | assert(fake.component.cacheCanvas === null, 'still cached');
364 | fake.cacheWhen(() => true);
365 | return wait(fake, 2);
366 | })
367 | .then(() => {
368 | assert(fake.component.cacheCanvas !== null, 'Did not create cache');
369 | })
370 | .then(done, done);
371 | });
372 |
373 | it('uses the right scale to cache', function (done) {
374 | const {vm, fake, easel, container} = buildVm();
375 | container.scale = 2;
376 | vm.scale = 2;
377 | Vue.nextTick()
378 | .then(() => {
379 | assert(fake.cacheScale === 2 * 2, 'cacheScale: ' + fake.cacheScale);
380 | })
381 | .then(done, done);
382 | });
383 |
384 | it('has scale in its updatesEaselCache', function () {
385 | assert(implementor.updatesEaselCache.indexOf('scale') >= 0, implementor.updatesEaselCache);
386 | });
387 | };
388 | };
389 |
--------------------------------------------------------------------------------
/test/includes/can-filter.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import assert from 'assert';
3 | import easeljs from '../../easeljs/easel.js';
4 | import VueEaseljs from '../../src/index.js';
5 |
6 | const wait = function (component, count = 1) {
7 | let promise = Promise.all([
8 | component.getCacheBounds(),
9 | Vue.nextTick(),
10 | ]);
11 | if (count > 1) {
12 | return promise.then(() => wait(component, count - 1));
13 | } else {
14 | return promise;
15 | }
16 | };
17 |
18 | export default function (implementor, extra_attributes = '', provide = {}) {
19 | return function () {
20 |
21 | const buildVm = function () {
22 | /**
23 | * A fake easel object
24 | */
25 | const easel = {
26 | addChild() {
27 | },
28 | removeChild() {
29 | },
30 | createCanvas(cb) {
31 | return cb();
32 | },
33 | };
34 |
35 | const vm = new Vue({
36 | template: `
37 |
38 |
44 |
45 |
46 | `,
47 | provide() {
48 | provide.easelParent = easel;
49 | provide.easelCanvas = easel;
50 | return provide;
51 | },
52 | data() {
53 | return {
54 | showFake: true,
55 | cache: false,
56 | filters: null,
57 | };
58 | },
59 | components: {
60 | implementor,
61 | },
62 | methods: {
63 | },
64 | }).$mount();
65 |
66 | const fake = vm.$refs.fake;
67 |
68 | return {fake, vm, easel};
69 | };
70 |
71 | it('should exist', function () {
72 | const {vm, fake} = buildVm();
73 | assert(fake);
74 | });
75 |
76 | it('should have $el', function () {
77 | const {vm, fake} = buildVm();
78 | assert(fake.$el);
79 | });
80 |
81 | it('should have component field', function () {
82 | const {vm, fake} = buildVm();
83 | assert(fake.component);
84 | });
85 |
86 | it('should have a filter', function (done) {
87 | const {vm, fake} = buildVm();
88 | vm.filters = [['BlurFilter', 5, 5, 1]];
89 | vm.cache = true;
90 | wait(fake, 2)
91 | .then(() => {
92 | assert(fake.component.cacheCanvas !== null, 'no cache');
93 | assert(fake.component.filters);
94 | assert(fake.component.filters.length === 1);
95 | assert(fake.component.filters[0] instanceof easeljs.BlurFilter);
96 | })
97 | .then(done, done);
98 | });
99 |
100 | it('should cache when filtering even when caching is not explicit', function (done) {
101 | const {vm, fake} = buildVm();
102 | assert(fake.component.cacheCanvas === null);
103 | wait(fake, 2)
104 | .then(() => {
105 | vm.cache = false;
106 | return wait(fake, 2);
107 | })
108 | .then(() => {
109 | assert(fake.component.cacheCanvas === null, 'still cached');
110 | vm.filters = [['BlurFilter', 5, 5, 1]];
111 | return wait(fake, 2);
112 | })
113 | .then(() => {
114 | assert(fake.component.cacheCanvas !== null, 'no cache');
115 | })
116 | .then(done, done);
117 | });
118 |
119 | it('should add and remove filters', function (done) {
120 | const {vm, fake} = buildVm();
121 | assert(fake.component.cacheCanvas === null);
122 | wait(fake, 2)
123 | .then(() => {
124 | vm.cache = false;
125 | return wait(fake, 2);
126 | })
127 | .then(() => {
128 | assert(fake.component.cacheCanvas === null, 'still cached');
129 | vm.filters = [['BlurFilter', 5, 5, 1]];
130 | return wait(fake, 2);
131 | })
132 | .then(() => {
133 | assert(fake.component.cacheCanvas !== null, 'no cache');
134 | vm.filters = null;
135 | return wait(fake, 2);
136 | })
137 | .then(() => {
138 | assert(fake.component.cacheCanvas === null, 'still cached 2');
139 | vm.cache = true;
140 | return wait(fake, 2);
141 | })
142 | .then(() => {
143 | assert(!fake.component.filters, 'still has filters');
144 | assert(fake.component.cacheCanvas !== null, 'no cache 2');
145 | })
146 | .then(done, done);
147 | });
148 |
149 | it('should fail on bad filters', function (done) {
150 | const {vm, fake} = buildVm();
151 | let caughtError;
152 | const originalError = console.error;
153 | console.error = (msg) => caughtError = msg;
154 | vm.filters = [['NO_SUCH_FILTER', 'whatever param']];
155 | wait(fake, 2)
156 | .then(() => {
157 | assert(!fake.component.filters || fake.component.filters.length === 0);
158 | assert(caughtError);
159 | })
160 | .finally(() => console.error = originalError)
161 | .then(done, done);
162 | });
163 |
164 | [
165 | ['BlurFilter', 5, 5, 1],
166 | ['ColorFilter', 0, 0, 0, 1, 0, 0, 255, 0],
167 | ['ColorMatrixFilter', 1, 1, 1, 1],
168 | ].forEach((filter) => {
169 | it(`should use filter ${filter[0]}`, function (done) {
170 | const {vm, fake} = buildVm();
171 | vm.filters = [filter];
172 | vm.cache = true;
173 | wait(fake, 2)
174 | .then(() => {
175 | assert(fake.component.cacheCanvas !== null, 'no cache');
176 | assert(fake.component.filters);
177 | assert(fake.component.filters.length === 1);
178 | })
179 | .then(done, done);
180 | });
181 | });
182 |
183 | it('should use a custom simple filter class', function (done) {
184 | const {vm, fake} = buildVm();
185 | const name = 'Custom' + new String(Math.random()).substr(-8);
186 | const Custom = class Custom {
187 | adjustImageData(){}
188 | };
189 | VueEaseljs.registerFilter(name, Custom);
190 | vm.filters = [[name]];
191 | wait(fake, 2)
192 | .then(() => {
193 | assert(fake.component.cacheCanvas !== null, 'no cache');
194 | assert(fake.component.filters);
195 | assert(fake.component.filters.length === 1);
196 | })
197 | .then(done, done);
198 | });
199 | };
200 | };
201 |
--------------------------------------------------------------------------------
/test/includes/does-events.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import {eventTypes} from '../../src/libs/easel-event-binder.js';
4 |
5 | assert(eventTypes && eventTypes.length > 0, 'easel-event-binder.js did not return a good eventTypes array');
6 |
7 | export default function (implementor, extra_attributes = '', provide = {}) {
8 |
9 | return function () {
10 |
11 | const buildVm = function () {
12 | const easel = {
13 | addChild(vueChild) {
14 | },
15 | removeChild(vueChild) {
16 | },
17 | };
18 |
19 | const eventHandlerCode = eventTypes.map(type => `@${type}="logEvent"`).join(' ');
20 |
21 | const vm = new Vue({
22 | template: `
23 |
24 |
28 |
29 |
30 | `,
31 | provide() {
32 | provide.easelParent = easel;
33 | provide.easelCanvas = easel;
34 | return provide;
35 | },
36 | data() {
37 | return {
38 | eventLog: [],
39 | };
40 | },
41 | components: {
42 | implementor,
43 | },
44 | methods: {
45 | logEvent(event) {
46 | this.eventLog.push(event);
47 | },
48 | clearEventLog() {
49 | this.eventLog = [];
50 | },
51 | },
52 | }).$mount();
53 |
54 | const fake = vm.$refs.fake;
55 |
56 | return {vm, fake};
57 | };
58 |
59 | it('should exist', function () {
60 | const {fake} = buildVm();
61 | assert(fake);
62 | });
63 |
64 | eventTypes.forEach(type => {
65 | it(`emits ${type} event`, function (done) {
66 | const {vm, fake} = buildVm();
67 | Vue.nextTick()
68 | .then(() => {
69 | vm.clearEventLog();
70 | fake.component.dispatchEvent(type);
71 | assert(vm.eventLog.length === 1, `wrong number of events ${vm.eventLog.length}`);
72 | })
73 | .then(done, done)
74 | });
75 | });
76 | };
77 | };
78 |
--------------------------------------------------------------------------------
/test/includes/is-a-display-object.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import doesEvents from './does-events.js';
4 | import easeljs from '../../easeljs/easel.js';
5 |
6 | /**
7 | * Returns a function to be used with `describe` for any component that is an
8 | * EaselDisplayObject.
9 | *
10 | * Like this:
11 | * `describe('is a display object that', isADisplayObject(MyComponent)`
12 | * @param VueComponent implementor
13 | * @return function
14 | */
15 | export default function (implementor, extra_attributes = '', provide = {}) {
16 |
17 | return function () {
18 |
19 | describe('does events and', doesEvents(implementor, extra_attributes, provide));
20 |
21 |
22 | const buildVm = function () {
23 | /**
24 | * A fake easel object. It allows adding and removing a child and has extra
25 | * methods to tell whether the object was added and removed.
26 | */
27 | const easel = {
28 | gotChild(vueChild) {
29 | return vueChild.added;
30 | },
31 | lostChild(vueChild) {
32 | return vueChild.removed;
33 | },
34 | addChild(vueChild) {
35 | vueChild.added = true;
36 | },
37 | removeChild(vueChild) {
38 | vueChild.removed = true;
39 | },
40 | };
41 |
42 | const vm = new Vue({
43 | template: `
44 |
45 |
60 |
61 |
62 | `,
63 | provide() {
64 | provide.easelParent = easel;
65 | provide.easelCanvas = easel;
66 | return provide;
67 | },
68 | data() {
69 | return {
70 | x: 1,
71 | y: 2,
72 | eventLog: [],
73 | showFake: true,
74 | flip: '',
75 | rotation: null,
76 | scale: 1,
77 | alpha: null,
78 | shadow: null,
79 | hAlign: 'left',
80 | vAlign: 'top',
81 | cursor: null,
82 | visible: null,
83 | name: null,
84 | };
85 | },
86 | components: {
87 | implementor,
88 | },
89 | methods: {
90 | logEvent(event) {
91 | this.eventLog.push(event);
92 | },
93 | clearEventLog() {
94 | this.eventLog = [];
95 | },
96 | },
97 | }).$mount();
98 |
99 | const fake = vm.$refs.fake;
100 |
101 | return {fake, vm, easel};
102 | };
103 |
104 | it('should exist', function () {
105 | const {fake, vm, easel} = buildVm();
106 | assert(fake);
107 | });
108 |
109 | it('should have same easel', function () {
110 | const {fake, vm, easel} = buildVm();
111 | assert(fake.easelParent === easel);
112 | });
113 |
114 | it('should have component field', function () {
115 | const {fake, vm, easel} = buildVm();
116 | assert(fake.component);
117 | });
118 |
119 | it('should have been added', function (done) {
120 | const {fake, vm, easel} = buildVm();
121 | Vue.nextTick()
122 | .then(() => {
123 | assert(easel.gotChild(fake));
124 | })
125 | .then(done, done);
126 | });
127 |
128 | it('should go away when gone', function (done) {
129 | const {fake, vm, easel} = buildVm();
130 | vm.showFake = false;
131 | Vue.nextTick()
132 | .then(() => {
133 | assert(easel.lostChild(fake));
134 | vm.showFake = true;
135 | return Vue.nextTick();
136 | })
137 | .then(done, done);
138 | });
139 |
140 | it('should have x and y', function (done) {
141 | const {fake, vm, easel} = buildVm();
142 | Vue.nextTick()
143 | .then(() => {
144 | assert(fake.component.x === 1);
145 | assert(fake.component.y === 2);
146 | })
147 | .then(done, done);
148 | });
149 |
150 | it('should change x and y', function (done) {
151 | const {fake, vm, easel} = buildVm();
152 | vm.x = 3;
153 | vm.y = 4;
154 | Vue.nextTick()
155 | .then(() => {
156 | assert(fake.component.x === 3);
157 | assert(fake.component.y === 4);
158 | })
159 | .then(done, done);
160 | });
161 |
162 | it('should not flip', function (done) {
163 | const {fake, vm, easel} = buildVm();
164 | vm.flip = '';
165 | Vue.nextTick()
166 | .then(() => {
167 | assert(fake.component.scaleX === 1);
168 | assert(fake.component.scaleY === 1);
169 | })
170 | .then(done, done);
171 | });
172 |
173 | it('should flip horizontal', function (done) {
174 | const {fake, vm, easel} = buildVm();
175 | vm.flip = 'horizontal';
176 | Vue.nextTick()
177 | .then(() => {
178 | assert(fake.component.scaleX === -1);
179 | assert(fake.component.scaleY === 1);
180 | })
181 | .then(done, done);
182 | });
183 |
184 | it('should flip vertical', function (done) {
185 | const {fake, vm, easel} = buildVm();
186 | vm.flip = 'vertical';
187 | Vue.nextTick()
188 | .then(() => {
189 | assert(fake.component.scaleX === 1);
190 | assert(fake.component.scaleY === -1);
191 | })
192 | .then(done, done);
193 | });
194 |
195 | it('should flip both', function (done) {
196 | const {fake, vm, easel} = buildVm();
197 | vm.flip = 'both';
198 | Vue.nextTick()
199 | .then(() => {
200 | assert(fake.component.scaleX === -1);
201 | assert(fake.component.scaleY === -1);
202 | })
203 | .then(done, done);
204 | });
205 |
206 | it('should not scale', function (done) {
207 | const {fake, vm, easel} = buildVm();
208 | vm.flip = '';
209 | Vue.nextTick()
210 | .then(() => {
211 | assert(fake.component.scaleX === 1);
212 | assert(fake.component.scaleY === 1);
213 | })
214 | .then(done, done);
215 | });
216 |
217 | it('should scale to double', function (done) {
218 | const {fake, vm, easel} = buildVm();
219 | vm.scale = 2;
220 | Vue.nextTick()
221 | .then(() => {
222 | assert(fake.component.scaleX === 2);
223 | assert(fake.component.scaleY === 2);
224 | })
225 | .then(done, done);
226 | });
227 |
228 | it('should scale and flip', function (done) {
229 | const {fake, vm, easel} = buildVm();
230 | vm.scale = 2;
231 | vm.flip = "both";
232 | Vue.nextTick()
233 | .then(() => {
234 | assert(fake.component.scaleX === -2);
235 | assert(fake.component.scaleY === -2);
236 | })
237 | .then(done, done);
238 | });
239 |
240 | it('should be 100% opaque', function () {
241 | const {fake, vm, easel} = buildVm();
242 | assert(fake.component.alpha === 1, "Wrong alpha: " + fake.component.alpha);
243 | });
244 |
245 | it('should become 50% opaque', function (done) {
246 | const {fake, vm, easel} = buildVm();
247 | vm.alpha = .5;
248 | Vue.nextTick()
249 | .then(() => {
250 | assert(fake.component.alpha === .5, "Wrong alpha: " + fake.component.alpha);
251 | })
252 | .then(done, done);
253 | });
254 |
255 | it('should have no shadow', function () {
256 | const {fake, vm, easel} = buildVm();
257 | assert(fake.component.shadow === null);
258 | });
259 |
260 | it('should have shadow', function (done) {
261 | const {fake, vm, easel} = buildVm();
262 | vm.shadow = ["black", 5, 7, 10];
263 | Vue.nextTick()
264 | .then(() => {
265 | assert(fake.component, 'missing component');
266 | assert(fake.component.shadow, 'missing shadow');
267 | assert(fake.component.shadow.color, 'missing color');
268 | assert(fake.component.shadow.color === 'black', 'Shadow color: ' + fake.component.shadow.color);
269 | assert(fake.component.shadow.offsetX === 5, 'Shadow offsetX: ' + fake.component.shadow.offsetX);
270 | assert(fake.component.shadow.offsetY === 7, 'Shadow offsetY: ' + fake.component.shadow.offsetY);
271 | assert(fake.component.shadow.blur === 10, 'Shadow blur: ' + fake.component.shadow.blur);
272 | })
273 | .then(done, done);
274 | });
275 |
276 | it('should have no shadow again', function (done) {
277 | const {fake, vm, easel} = buildVm();
278 | vm.shadow = ["black", 5, 7, 10];
279 | Vue.nextTick()
280 | .then(() => {
281 | assert(fake.component, 'missing component');
282 | assert(fake.component.shadow, 'missing shadow');
283 | vm.shadow = null;
284 | return Vue.nextTick();
285 | })
286 | .then(() => {
287 | assert(fake.component.shadow === null);
288 | })
289 | .then(done, done);
290 | });
291 |
292 | describe('visibility', function () {
293 |
294 | const waitUntilVisible = function (fake) {
295 | return new Promise(resolve => {
296 | fake.$watch('component.visible', () => {
297 | if (fake.component.visible) {
298 | resolve();
299 | }
300 | }, {immediate: true});
301 | });
302 | };
303 |
304 | const waitUntilInvisible = function (fake) {
305 | return new Promise(resolve => {
306 | fake.$watch('component.visible', () => {
307 | if (!fake.component.visible) {
308 | resolve();
309 | }
310 | }, {immediate: true});
311 | });
312 | };
313 |
314 | it('should be visible', function (done) {
315 | const {fake, vm, easel} = buildVm();
316 | vm.visible = true;
317 | Vue.nextTick()
318 | .then(() => waitUntilVisible(fake))
319 | .then(done, done);
320 | });
321 |
322 | it('should be invisible explicitly', function (done) {
323 | const {fake, vm, easel} = buildVm();
324 | vm.visible = false;
325 | Vue.nextTick()
326 | .then(() => waitUntilInvisible(fake))
327 | .then(done, done);
328 | });
329 |
330 | it('should be invisible due to visibility blocker promise', function (done) {
331 | const {fake, vm, easel} = buildVm();
332 | vm.visible = true;
333 | fake.remainInvisibleUntil(new Promise(() => void(0)));
334 | Vue.nextTick()
335 | .then(() => waitUntilInvisible(fake))
336 | .then(done, done);
337 | });
338 |
339 | it('should be visible when blocker promise is already resolved', function (done) {
340 | const {fake, vm, easel} = buildVm();
341 | vm.visible = true;
342 | fake.remainInvisibleUntil(Promise.resolve());
343 | Vue.nextTick()
344 | .then(() => waitUntilVisible(fake))
345 | .then(done, done);
346 | });
347 |
348 | it('should be visible when blocker promise is already rejected', function (done) {
349 | const {fake, vm, easel} = buildVm();
350 | vm.visible = true;
351 | fake.remainInvisibleUntil(Promise.reject());
352 | Vue.nextTick()
353 | .then(() => waitUntilVisible(fake))
354 | .then(done, done);
355 | });
356 |
357 | it('should become visible when blocker promise resolves', function (done) {
358 | const {fake, vm, easel} = buildVm();
359 | vm.visible = false;
360 | let resolve;
361 | fake.remainInvisibleUntil(new Promise(r => resolve = r));
362 | Vue.nextTick()
363 | .then(() => waitUntilInvisible(fake))
364 | .then(() => resolve())
365 | .then(() => vm.visible = true)
366 | .then(() => waitUntilVisible(fake))
367 | .then(done, done);
368 | });
369 |
370 | it('should become visible when blocker promise rejects', function (done) {
371 | const {fake, vm, easel} = buildVm();
372 | vm.visible = false;
373 | let reject;
374 | fake.remainInvisibleUntil(new Promise((z, r) => reject = r));
375 | Vue.nextTick()
376 | .then(() => waitUntilInvisible(fake))
377 | .then(() => reject())
378 | .then(() => vm.visible = true)
379 | .then(() => waitUntilVisible(fake))
380 | .then(done, done);
381 | });
382 | });
383 |
384 | // These fields are simply copied through to the component, so we test
385 | // that that copying works as intended.
386 |
387 | const passthrough = {
388 | cursor: 'pointer',
389 | rotation: 15,
390 | name: 'Charles Wallace',
391 | };
392 |
393 | Object.keys(passthrough).forEach(function (field) {
394 | const value = passthrough[field];
395 |
396 | it(`should have ${field} = null`, function (done) {
397 | const {fake, vm, easel} = buildVm();
398 | Vue.nextTick()
399 | .then(() => {
400 | assert(fake.component[field] === null, `Wrong ${field}: ${fake.component[field]}`);
401 | })
402 | .then(done, done);
403 | });
404 |
405 | it(`should have ${field} = ${value}`, function (done) {
406 | const {fake, vm, easel} = buildVm();
407 | vm[field] = value;
408 | Vue.nextTick()
409 | .then(() => {
410 | assert(fake.component, 'missing component');
411 | assert(fake.component[field] === value, `Wrong ${field}: ${fake.component[field]}`);
412 | })
413 | .then(done, done);
414 | });
415 |
416 | it(`should have ${field} = ${value}, then null again`, function (done) {
417 | const {fake, vm, easel} = buildVm();
418 | vm[field] = value;
419 | Vue.nextTick()
420 | .then(() => {
421 | assert(fake.component, 'missing component');
422 | assert(fake.component[field] === value, `Wrong ${field}: ${fake.component[field]}`);
423 | vm[field] = null;
424 | return Vue.nextTick();
425 | })
426 | .then(() => {
427 | assert(fake.component[field] === null);
428 | })
429 | .then(done, done);
430 | });
431 | });
432 |
433 | it('should default to x=0,y=0', function (done) {
434 | const {fake, vm, easel} = buildVm();
435 | vm.x = undefined;
436 | vm.y = undefined;
437 | Vue.nextTick()
438 | .then(() => {
439 | assert(fake.component.x === 0, 'Wrong default x in: ' + fake.component.x);
440 | assert(fake.component.y === 0, 'Wrong default y in: ' + fake.component.y);
441 | })
442 | .then(done, done);
443 | });
444 | };
445 | };
446 |
--------------------------------------------------------------------------------
/test/includes/is-alignable.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 |
4 | const wait = function (component) {
5 | return Promise.all([
6 | component.getAlignDimensions(),
7 | Vue.nextTick(),
8 | ]);
9 | };
10 |
11 | export default function isAlignable(implementor, {width, height}, extra_attributes = '', provide = {}) {
12 |
13 | return function () {
14 |
15 | const buildVm = function () {
16 | /**
17 | * A fake easel object. It allows adding and removing a child and has extra
18 | * methods to tell whether the object was added and removed.
19 | */
20 | const easel = {
21 | addChild(vueChild) {
22 | },
23 | removeChild(vueChild) {
24 | },
25 | };
26 |
27 | const vm = new Vue({
28 | template: `
29 |
30 |
42 |
43 |
44 | `,
45 | provide() {
46 | provide.easelParent = easel;
47 | provide.easelCanvas = easel;
48 | return provide;
49 | },
50 | data() {
51 | return {
52 | x: 1,
53 | y: 2,
54 | eventLog: [],
55 | showFake: true,
56 | flip: '',
57 | rotation: null,
58 | scale: 1,
59 | alpha: null,
60 | shadow: null,
61 | align: ['left', 'top'],
62 | };
63 | },
64 | components: {
65 | implementor,
66 | },
67 | methods: {
68 | logEvent(event) {
69 | this.eventLog.push(event);
70 | },
71 | clearEventLog() {
72 | this.eventLog = [];
73 | },
74 | },
75 | }).$mount();
76 |
77 | const fake = vm.$refs.fake;
78 |
79 | return {vm, fake};
80 | };
81 |
82 | /**
83 | * Alignment tests are only done here, because in some components, they
84 | * depend too much on having a real easel.
85 | */
86 |
87 | it('should have the right given alignment', function () {
88 | const {vm, fake} = buildVm();
89 | assert(fake.component.regX === 0, 'Wrong regX: ' + fake.component.regX);
90 | assert(fake.component.regY === 0, 'Wrong regY: ' + fake.component.regY);
91 | });
92 |
93 | it('should be able to change the alignment', function (done) {
94 | const {vm, fake} = buildVm();
95 | wait(fake)
96 | .then(() => {
97 | vm.align = ['right', 'bottom'];
98 | return wait(fake);
99 | })
100 | .then(() => {
101 | assert(fake.component.regX === width, 'Wrong regX in: ' + fake.component.regX);
102 | assert(fake.component.regY === height, 'Wrong regY in: ' + fake.component.regY);
103 | })
104 | .then(done, done);
105 | });
106 |
107 | it('should set the right default alignment', function (done) {
108 | const {vm, fake} = buildVm();
109 | wait(fake)
110 | .then(() => {
111 | vm.align = ['', ''];
112 | return wait(fake);
113 | })
114 | .then(() => {
115 | assert(fake.component.regX === 0, 'Wrong regX in: ' + fake.component.regX);
116 | assert(fake.component.regY === 0, 'Wrong regY in: ' + fake.component.regY);
117 | })
118 | .then(done, done);
119 | });
120 |
121 | it('should normalize reversed array align prop', function (done) {
122 | const {vm, fake} = buildVm();
123 | wait(fake)
124 | .then(() => {
125 | vm.align = ['bottom', 'right'];
126 | return wait(fake);
127 | })
128 | .then(() => {
129 | assert(fake.component.regX === width, 'Wrong regX in: ' + fake.component.regX);
130 | assert(fake.component.regY === height, 'Wrong regY in: ' + fake.component.regY);
131 | })
132 | .then(done, done);
133 | });
134 |
135 | it('should normalize reversed string align prop', function (done) {
136 | const {vm, fake} = buildVm();
137 | wait(fake)
138 | .then(() => {
139 | vm.align = 'bottom-left';
140 | return wait(fake);
141 | })
142 | .then(() => {
143 | assert(fake.component.regX === 0, 'Wrong regX in: ' + fake.component.regX);
144 | assert(fake.component.regY === height, 'Wrong regY in: ' + fake.component.regY);
145 | })
146 | .then(done, done);
147 | });
148 |
149 | it('should default undefined align prop', function (done) {
150 | const {vm, fake} = buildVm();
151 | wait(fake)
152 | .then(() => {
153 | vm.align = ['bottom', 'right'];
154 | return wait(fake);
155 | })
156 | .then(() => {
157 | vm.align = undefined;
158 | return wait(fake);
159 | })
160 | .then(() => {
161 | assert(fake.component.regX === 0, 'Wrong regX in: ' + fake.component.regX);
162 | assert(fake.component.regY === 0, 'Wrong regY in: ' + fake.component.regY);
163 | })
164 | .then(done, done);
165 | });
166 | };
167 | };
168 |
--------------------------------------------------------------------------------
/test/includes/is-an-easel-parent.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import EaselFake from '../fixtures/EaselFake.js';
3 | import Vue from 'vue';
4 |
5 | /**
6 | * Returns a function to be used with `describe` for any component using
7 | * EaselParent.
8 | *
9 | * Like this:
10 | * `describe('is a parent', isAnEaselParent(MyComponentThatIsAParent)`
11 | * @param VueComponent implementor
12 | * @return function
13 | */
14 | export default function isAnEaselParent(implementor) {
15 |
16 | return function () {
17 |
18 | const buildVm = function () {
19 | const vm = new Vue({
20 | template: `
21 |
22 |
29 |
30 |
37 |
38 |
45 |
46 |
47 | `,
48 | components: {
49 | 'implementor': implementor,
50 | 'easel-fake': EaselFake,
51 | },
52 | provide() {
53 | return {
54 | easelParent: {
55 | addChild() {
56 | },
57 | removeChild() {
58 | },
59 | },
60 | easelCanvas: {},
61 | };
62 | },
63 | data() {
64 | return {
65 | showOne: false,
66 | showTwo: false,
67 | list: [],
68 | };
69 | },
70 | }).$mount();
71 |
72 | const parent = vm.$refs.parent;
73 |
74 | return {vm, parent};
75 | };
76 |
77 | it('should have 0 children', function (done) {
78 | const {vm, parent} = buildVm();
79 | Vue.nextTick()
80 | .then(() => {
81 | assert(parent.children, 'parent has no children field');
82 | assert(parent.children.length === 0, 'parent has too many children: ' + parent.children.length);
83 | })
84 | .then(done, done);
85 | });
86 |
87 | it('should get a child', function (done) {
88 | const {vm, parent} = buildVm();
89 | vm.showOne = true;
90 | Vue.nextTick()
91 | .then(() => {
92 | assert(parent.children.length === 1, 'parent has wrong number of children: ' + parent.children.length);
93 | assert(vm.$refs.one === parent.children[0], 'parent has wrong child');
94 | })
95 | .then(done, done);
96 | });
97 |
98 | it('should lose a child', function (done) {
99 | const {vm, parent} = buildVm();
100 | vm.showOne = false;
101 | Vue.nextTick()
102 | .then(() => {
103 | assert(parent.children.length === 0, 'parent still has children: ' + parent.children.length);
104 | assert(!vm.$refs.one, 'child `one` still exists');
105 | })
106 | .then(done, done);
107 | });
108 |
109 | it('should get two children', function (done) {
110 | const {vm, parent} = buildVm();
111 | vm.showOne = true;
112 | vm.showTwo = true;
113 | Vue.nextTick()
114 | .then(() => {
115 | assert(parent.children.length === 2, 'parent does not have right children' + parent.children.length);
116 | assert(vm.$refs.one === parent.children[0], 'child `one` is not in the right place');
117 | assert(vm.$refs.two === parent.children[1], 'child `two` is not in the right place');
118 | })
119 | .then(done, done);
120 | });
121 |
122 | it('should lose two children', function (done) {
123 | const {vm, parent} = buildVm();
124 | vm.showOne = false;
125 | vm.showTwo = false;
126 | Vue.nextTick()
127 | .then(() => {
128 | assert(parent.children.length === 0, 'parent still has children: ' + parent.children.length);
129 | assert(!vm.$refs.one, 'child `one` still exists');
130 | assert(!vm.$refs.two, 'child `two` still exists');
131 | })
132 | .then(done, done);
133 | });
134 |
135 | it('should get two children, one by one', function (done) {
136 | const {vm, parent} = buildVm();
137 | vm.showOne = true;
138 | let one, two;
139 | Vue.nextTick()
140 | .then(() => {
141 | assert(parent.children.length === 1, 'parent does not have right children' + parent.children.length);
142 | one = vm.$refs.one;
143 | vm.showTwo = true;
144 | return Vue.nextTick();
145 | })
146 | .then(() => {
147 | two = vm.$refs.two
148 | assert(parent.children.length === 2, 'parent does not have right children' + parent.children.length);
149 | assert(vm.$refs.one === parent.children[0], 'child `one` is not in the right place');
150 | assert(vm.$refs.two === parent.children[1], 'child `two` is not in the right place');
151 | assert(one === vm.$refs.one, 'one changed to a new object');
152 | })
153 | .then(done, done);
154 | });
155 |
156 | it('should get two children, one by one, in reverse', function (done) {
157 | const {vm, parent} = buildVm();
158 | let two;
159 | vm.showOne = false;
160 | vm.showTwo = false;
161 |
162 | Vue.nextTick()
163 | .then(() => {
164 | vm.showTwo = true;
165 | return Vue.nextTick();
166 | })
167 | .then(() => {
168 | assert(parent.children.length === 1, 'parent does not have right children' + parent.children.length);
169 | assert(vm.$refs.two.component === parent.component.getChildAt(0), 'child `two` is not in the right place');
170 | two = vm.$refs.two;
171 | vm.showOne = true;
172 | return Vue.nextTick();
173 | })
174 | .then(() => {
175 | assert(parent.children.length === 2, 'parent does not have right children' + parent.children.length);
176 | assert(vm.$refs.one.component === parent.component.getChildAt(0), 'child `one` is not in the right place');
177 | assert(vm.$refs.two.component === parent.component.getChildAt(1), 'child `two` is not in the right place');
178 | assert(two === vm.$refs.two, 'two changed to a new object');
179 | })
180 | .then(done, done);
181 | });
182 |
183 | it('should get two children, then switch their locations', function (done) {
184 | const {vm, parent} = buildVm();
185 | vm.showOne = false;
186 | vm.showTwo = false;
187 |
188 | Vue.nextTick()
189 | .then(() => {
190 | vm.list.push('bob');
191 | vm.list.push('carol');
192 | return Vue.nextTick();
193 | })
194 | .then(() => {
195 | assert(parent.children.length === 2, 'parent does not have right children' + parent.children.length);
196 | assert(vm.$refs.bob[0].component === parent.component.getChildAt(0), 'before switch, child `bob` is not in the right place');
197 | assert(vm.$refs.carol[0].component === parent.component.getChildAt(1), 'before switch, child `carol` is not in the right place');
198 | vm.list.pop();
199 | vm.list.pop();
200 | vm.list.push('carol');
201 | vm.list.push('bob');
202 | return Vue.nextTick();
203 | })
204 | .then(() => {
205 | assert(parent.children.length === 2, 'parent does not have right children' + parent.children.length);
206 | assert(vm.$refs.carol[0].component === parent.component.getChildAt(0), 'after switch, child `carol` is not in the right place');
207 | assert(vm.$refs.bob[0].component === parent.component.getChildAt(1), 'after switch, child `bob` is not in the right place');
208 | })
209 | .then(done, done);
210 | });
211 |
212 | it('should get the right parent on the child.component', function (done) {
213 | const {vm, parent} = buildVm();
214 | vm.showOne = true;
215 | Vue.nextTick()
216 | .then(() => {
217 | var one = vm.$refs.one;
218 | assert(one.component.parent === parent.component);
219 | })
220 | .then(done, done);
221 | });
222 | };
223 | };
224 |
--------------------------------------------------------------------------------
/test/normalize-alignment.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import normalizeAlignment, {horizontalValues, verticalValues} from '../src/libs/normalize-alignment.js';
3 |
4 | describe('alignment-normalizer.js', function () {
5 |
6 | it('exists', function () {
7 | assert(normalizeAlignment);
8 | });
9 |
10 | it('leaves normalized arrays alone', function () {
11 | const correct = ['left', 'top'];
12 | const normalized = normalizeAlignment(correct);
13 | assert(correct.join() === normalized.join());
14 | });
15 |
16 | it('normalizes backwards arrays', function () {
17 | const incorrect = ['top', 'left'];
18 | const correct = ['left', 'top'];
19 | const normalized = normalizeAlignment(incorrect);
20 | assert(correct.join() === normalized.join());
21 | });
22 |
23 | it('should not choke on center,center', function () {
24 | assert(normalizeAlignment(['center', 'center']));
25 | });
26 |
27 | it('gets upset about double vertical', function () {
28 | let caught;
29 | try {
30 | normalizeAlignment(['top', 'top']);
31 | } catch (e) {
32 | caught = e;
33 | }
34 | assert(caught, 'normalizeAlignment was fine with top,top');
35 | });
36 |
37 | it('gets upset about double horizontal', function () {
38 | let caught;
39 | try {
40 | normalizeAlignment(['left', 'left']);
41 | } catch (e) {
42 | caught = e;
43 | }
44 | assert(caught, 'normalizeAlignment was fine with left,left');
45 | });
46 |
47 | it('gets upset about unknown values', function () {
48 | let caught;
49 | try {
50 | normalizeAlignment(['no-such-value', 'no-such-value']);
51 | } catch (e) {
52 | caught = e;
53 | }
54 | assert(caught, 'normalizeAlignment was fine with no-such-value,no-such-value');
55 | });
56 |
57 | describe('it can handle a string in normal order', function () {
58 | const correct = ['left', 'top'];
59 | const string = 'left-top';
60 | const normal = normalizeAlignment(string);
61 | assert(correct.join() === normal.join());
62 | });
63 |
64 | describe('it can handle a string in reverse order', function () {
65 | const correct = ['left', 'top'];
66 | const string = 'top-left';
67 | const normal = normalizeAlignment(string);
68 | assert(correct.join() === normal.join());
69 | });
70 |
71 | describe('it can handle extra whitespace', function () {
72 | const correct = ['left', 'top'];
73 | const string = " left-top\t";
74 | const normal = normalizeAlignment(string);
75 | assert(correct.join() === normal.join());
76 | });
77 |
78 | describe('it makes no change for ,center, even though they are ambiguous', function () {
79 | const correct = ['', 'center'];
80 | const normal = normalizeAlignment(correct);
81 | assert(correct.join() === normal.join());
82 | });
83 |
84 | describe('it makes no change for center,, even though they are ambiguous', function () {
85 | const correct = ['center', ''];
86 | const normal = normalizeAlignment(correct);
87 | assert(correct.join() === normal.join());
88 | });
89 |
90 | describe('is ok with all permutations, so it', function () {
91 | horizontalValues.forEach(function (h) {
92 | verticalValues.forEach(function (v) {
93 |
94 | it(`should handle [${h},${v}]`, function () {
95 | const correct = [h, v];
96 | const normal = normalizeAlignment(correct);
97 | assert(correct.join() === normal.join());
98 | });
99 |
100 | it(`should handle '${h}-${v}'`, function () {
101 | const correct = [h, v];
102 | const string = `${h}-${v}`;
103 | const normal = normalizeAlignment(string);
104 | assert(correct.join() === normal.join(), correct.join() + ' !== ' + normal.join());
105 | });
106 |
107 | // Only run the following tests for a pairs of values that
108 | // aren't in both sets, like '' and 'center'.
109 | //
110 | // A pair of values that ARE in both sets should remain
111 | // unchanged.
112 | const hIsAmbiguous = verticalValues.indexOf(h) > -1;
113 | const vIsAmbiguous = horizontalValues.indexOf(v) > -1;
114 | const bothAreAmbiguous = hIsAmbiguous && vIsAmbiguous;
115 | if (!bothAreAmbiguous) {
116 | it(`should handle [${v},${h}]`, function () {
117 | const correct = [h, v];
118 | const incorrect = [v, h];
119 | const normal = normalizeAlignment(incorrect);
120 | assert(correct.join() === normal.join());
121 | });
122 |
123 | it(`should handle '${v}-${h}'`, function () {
124 | const correct = [h, v];
125 | const string = `${v}-${h}`;
126 | const normal = normalizeAlignment(string);
127 | assert(correct.join() === normal.join(), correct.join() + ' !== ' + normal.join());
128 | });
129 | }
130 |
131 | });
132 | });
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/test/sort-by-dom.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Vue from 'vue';
3 | import shuffle from 'lodash.shuffle';
4 | import sortByDom, {sorter} from '../src/libs/sort-by-dom.js';
5 |
6 | describe('sort-by-dom', function () {
7 |
8 | const nester = {
9 | props: ['name'],
10 | template: `
11 |
12 |
13 |
14 | `,
15 | };
16 |
17 | const vm = new Vue({
18 | template: `
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | `,
27 | components: {
28 | nester,
29 | },
30 | }).$mount();
31 |
32 | const {
33 | nester_1,
34 | nester_1_1,
35 | nester_1_2,
36 | nester_1_2_1,
37 | nester_1_3,
38 | } = vm.$refs;
39 |
40 | it('should not change the order if its already correct', function () {
41 | const correct = [
42 | nester_1,
43 | nester_1_1,
44 | nester_1_2,
45 | nester_1_2_1,
46 | nester_1_3,
47 | ];
48 | const sorted = sortByDom(correct);
49 | assert(correct.length === sorted.length, 'Different length correct & sorted');
50 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
51 | });
52 |
53 | it('should put a parent before its child', function () {
54 | const correct = [
55 | nester_1,
56 | nester_1_1,
57 | ];
58 | const mixed = [
59 | nester_1_1,
60 | nester_1,
61 | ];
62 | const sorted = sortByDom(mixed);
63 | assert(correct.length === sorted.length, 'Different length correct & sorted');
64 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
65 | });
66 |
67 | it('should put a late sibling before an early sibling', function () {
68 | const correct = [
69 | nester_1_1,
70 | nester_1_2,
71 | ];
72 | const mixed = [
73 | nester_1_2,
74 | nester_1_1,
75 | ];
76 | const sorted = sortByDom(mixed);
77 | assert(correct.length === sorted.length, 'Different length correct & sorted');
78 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
79 | });
80 |
81 | it('should throw an exception if one is disconnected', function () {
82 | let caught;
83 | const el1 = document.createElement('img');
84 | const array = [
85 | {$el: el1, name: 'Disconnected image 1'},
86 | nester_1,
87 | ];
88 | try {
89 | sortByDom(array);
90 | } catch (e) {
91 | caught = e;
92 | }
93 | assert(caught, 'No error on disconnected');
94 | });
95 |
96 | it('should throw an exception if all are disconnected', function () {
97 | let caught;
98 | const el1 = document.createElement('img');
99 | const el2 = document.createElement('img');
100 | const array = [
101 | {$el: el1, name: 'Disconnected image 1'},
102 | {$el: el2, name: 'Disconnected image 2'},
103 | ];
104 | try {
105 | sortByDom(array);
106 | } catch (e) {
107 | caught = e;
108 | }
109 | assert(caught, 'No error on disconnected');
110 | });
111 |
112 | it('should not change all elements are the same element', function () {
113 | const correct = [
114 | nester_1,
115 | nester_1,
116 | nester_1,
117 | nester_1,
118 | nester_1,
119 | ];
120 | const sorted = sortByDom(correct);
121 | assert(correct.length === sorted.length, 'Different length correct & sorted');
122 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
123 | });
124 |
125 | it('should sort a randomly mixed group', function () {
126 | const correct = [
127 | nester_1,
128 | nester_1_1,
129 | nester_1_2,
130 | nester_1_2_1,
131 | nester_1_3,
132 | ];
133 | const mixed = shuffle(correct);
134 | const sorted = sortByDom(mixed);
135 | assert(correct.length === sorted.length, 'Different length correct & sorted');
136 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
137 | });
138 |
139 | it('should allow using the sorter in other sort functions, such as Array.sort', function () {
140 | const correct = [
141 | nester_1,
142 | nester_1_1,
143 | nester_1_2,
144 | nester_1_2_1,
145 | nester_1_3,
146 | ];
147 | const mixed = shuffle(correct);
148 | const sorted = mixed.sort(sorter);
149 | assert(correct.length === sorted.length, 'Different length correct & sorted');
150 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
151 | });
152 |
153 | it('should allow using the sorter in other sort functions, such as bubble sort', function () {
154 | const correct = [
155 | nester_1,
156 | nester_1_1,
157 | nester_1_2,
158 | nester_1_2_1,
159 | nester_1_3,
160 | ];
161 | const mixed = shuffle(correct);
162 | const bubbleSort = function (source) {
163 | const array = [...source];
164 | for (let i = 0; i < array.length; i++) {
165 | for (let j = i + 1; j < array.length; j++) {
166 | const compare = sorter(array[i], array[j]);
167 | if (compare <= 0) {
168 | // already correct
169 | } else {
170 | const temp = array[i];
171 | array[i] = array[j];
172 | array[j] = temp;
173 | }
174 | }
175 | }
176 | return array;
177 | };
178 | const sorted = bubbleSort(mixed);
179 | assert(correct.length === sorted.length, 'Different length correct & sorted');
180 | sorted.forEach((element, i) => assert(element === correct[i], `error at index ${i}: wrong=${element.name}, right=${correct[i].name}`));
181 | });
182 | });
183 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | // test/index.js
2 | import Vue from 'vue';
3 | import SetShim from './fixtures/Set';
4 |
5 | // Provide Promise since Phantom doesn't have it
6 | if (!global.Promise) {
7 | global.Promise = require('promise');
8 | }
9 |
10 | // Provide Set since Phantom doesn't have it
11 | if (!global.Set) {
12 | global.Set = SetShim;
13 | }
14 |
15 | // Destroy Vue instances automatically:
16 | const destroyable = [];
17 |
18 | afterEach(() => {
19 | destroyable.forEach(vue => vue.$destroy());
20 | destroyable.splice(0);
21 | });
22 |
23 | Vue.mixin({
24 | mounted() {
25 | destroyable.push(this);
26 | }
27 | });
28 |
29 | // require all test files using special Webpack feature
30 | // https://webpack.github.io/docs/context.html#require-context
31 | const testsContext = require.context('.', true, /\.spec$/)
32 | testsContext.keys().forEach(testsContext)
33 |
--------------------------------------------------------------------------------