punter.js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/LICENSE.MD +21 -0
- package/README.MD +731 -0
- package/games/pong.html +260 -0
- package/images/pong/sprites/ball.png +0 -0
- package/images/pong/sprites/paddle.png +0 -0
- package/images/punterjs.png +0 -0
- package/package.json +33 -0
- package/punter.js +1514 -0
- package/tests/fixtures/index.html +30 -0
- package/tests/fixtures/silence.wav +0 -0
- package/tests/input-spec.js +104 -0
- package/tests/interface-spec.js +200 -0
- package/tests/jasmine/config.json +20 -0
- package/tests/jasmine/reporter.js +25 -0
- package/tests/jasmine/teardown.js +7 -0
- package/tests/jasmine/timeout.js +1 -0
- package/tests/scenes-spec.js +87 -0
- package/tests/setup.js +49 -0
- package/tests/sounds-spec.js +83 -0
- package/tests/sprites-spec.js +259 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Punter.js Test Fixture</title>
|
|
6
|
+
<style>
|
|
7
|
+
html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; }
|
|
8
|
+
</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<canvas id="game" width="375" height="667"></canvas>
|
|
12
|
+
<script src="/punter.js"></script>
|
|
13
|
+
<script>
|
|
14
|
+
// 1x1 red pixel PNG as data URL (valid image for sprite loading)
|
|
15
|
+
var heroImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwADhQGAWjR9awAAAABJRU5ErkJggg==';
|
|
16
|
+
|
|
17
|
+
window.__ready = false;
|
|
18
|
+
|
|
19
|
+
punter.on('ready', function () {
|
|
20
|
+
window.__ready = true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
punter.init({
|
|
24
|
+
canvas: '#game',
|
|
25
|
+
sprites: { hero: heroImg },
|
|
26
|
+
sounds: { beep: '/tests/fixtures/silence.wav' }
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var setup = require('./setup');
|
|
4
|
+
|
|
5
|
+
describe('Input', function () {
|
|
6
|
+
|
|
7
|
+
var page;
|
|
8
|
+
|
|
9
|
+
beforeAll(async function () {
|
|
10
|
+
page = await setup.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async function () {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async function () {
|
|
18
|
+
await page.evaluate(function () { punter.clearInput(); });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// --- keys ---
|
|
22
|
+
|
|
23
|
+
it('keys is an object that tracks keyboard state', async function () {
|
|
24
|
+
var result = await page.evaluate(function () {
|
|
25
|
+
return typeof punter.keys;
|
|
26
|
+
});
|
|
27
|
+
expect(result).toBe('object');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('keydown event sets the key to true', async function () {
|
|
31
|
+
await page.keyboard.down('ArrowLeft');
|
|
32
|
+
var result = await page.evaluate(function () {
|
|
33
|
+
return punter.keys['ArrowLeft'];
|
|
34
|
+
});
|
|
35
|
+
expect(result).toBe(true);
|
|
36
|
+
await page.keyboard.up('ArrowLeft');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('keyup event sets the key to false', async function () {
|
|
40
|
+
await page.keyboard.down('ArrowRight');
|
|
41
|
+
await page.keyboard.up('ArrowRight');
|
|
42
|
+
var result = await page.evaluate(function () {
|
|
43
|
+
return punter.keys['ArrowRight'];
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('clearInput resets all key states to false', async function () {
|
|
49
|
+
await page.keyboard.down('ArrowLeft');
|
|
50
|
+
await page.keyboard.down('Space');
|
|
51
|
+
// small delay to let DOM events propagate
|
|
52
|
+
await page.evaluate(function () {
|
|
53
|
+
return new Promise(function (r) { setTimeout(r, 10); });
|
|
54
|
+
});
|
|
55
|
+
var result = await page.evaluate(function () {
|
|
56
|
+
punter.clearInput();
|
|
57
|
+
return { left: punter.keys['ArrowLeft'], space: punter.keys[' '] };
|
|
58
|
+
});
|
|
59
|
+
expect(result.left).toBe(false);
|
|
60
|
+
expect(result.space).toBe(false);
|
|
61
|
+
await page.keyboard.up('ArrowLeft');
|
|
62
|
+
await page.keyboard.up('Space');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// --- mouse ---
|
|
66
|
+
|
|
67
|
+
it('mouse exposes x, y and clicked properties', async function () {
|
|
68
|
+
var result = await page.evaluate(function () {
|
|
69
|
+
return {
|
|
70
|
+
hasX: typeof punter.mouse.x === 'number',
|
|
71
|
+
hasY: typeof punter.mouse.y === 'number',
|
|
72
|
+
hasClicked: typeof punter.mouse.clicked === 'boolean'
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
expect(result.hasX).toBe(true);
|
|
76
|
+
expect(result.hasY).toBe(true);
|
|
77
|
+
expect(result.hasClicked).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('mouse.clicked starts as false', async function () {
|
|
81
|
+
var result = await page.evaluate(function () {
|
|
82
|
+
punter.clearInput();
|
|
83
|
+
return punter.mouse.clicked;
|
|
84
|
+
});
|
|
85
|
+
expect(result).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('mousedown sets mouse.clicked to true', async function () {
|
|
89
|
+
await page.mouse.click(100, 100);
|
|
90
|
+
var result = await page.evaluate(function () {
|
|
91
|
+
return punter.mouse.clicked;
|
|
92
|
+
});
|
|
93
|
+
expect(result).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('clearInput resets mouse.clicked to false', async function () {
|
|
97
|
+
await page.mouse.click(100, 100);
|
|
98
|
+
var result = await page.evaluate(function () {
|
|
99
|
+
punter.clearInput();
|
|
100
|
+
return punter.mouse.clicked;
|
|
101
|
+
});
|
|
102
|
+
expect(result).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var setup = require('./setup');
|
|
4
|
+
|
|
5
|
+
describe('Interface', function () {
|
|
6
|
+
|
|
7
|
+
var page;
|
|
8
|
+
|
|
9
|
+
beforeAll(async function () {
|
|
10
|
+
page = await setup.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async function () {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// --- public API surface ---
|
|
18
|
+
|
|
19
|
+
it('exposes the expected methods', async function () {
|
|
20
|
+
var result = await page.evaluate(function () {
|
|
21
|
+
return {
|
|
22
|
+
init: typeof punter.init,
|
|
23
|
+
scene: typeof punter.scene,
|
|
24
|
+
go: typeof punter.go,
|
|
25
|
+
on: typeof punter.on,
|
|
26
|
+
createSprite: typeof punter.createSprite,
|
|
27
|
+
getSprite: typeof punter.getSprite,
|
|
28
|
+
playSound: typeof punter.playSound,
|
|
29
|
+
stopSound: typeof punter.stopSound,
|
|
30
|
+
pause: typeof punter.pause,
|
|
31
|
+
resume: typeof punter.resume,
|
|
32
|
+
clearInput: typeof punter.clearInput,
|
|
33
|
+
redraw: typeof punter.redraw
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
Object.keys(result).forEach(function (key) {
|
|
37
|
+
expect(result[key]).toBe('function');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('exposes keys and mouse objects', async function () {
|
|
42
|
+
var result = await page.evaluate(function () {
|
|
43
|
+
return {
|
|
44
|
+
keysType: typeof punter.keys,
|
|
45
|
+
mouseType: typeof punter.mouse
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
expect(result.keysType).toBe('object');
|
|
49
|
+
expect(result.mouseType).toBe('object');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// --- computed properties ---
|
|
53
|
+
|
|
54
|
+
it('width and height return the canvas dimensions', async function () {
|
|
55
|
+
var result = await page.evaluate(function () {
|
|
56
|
+
return { width: punter.width, height: punter.height };
|
|
57
|
+
});
|
|
58
|
+
expect(typeof result.width).toBe('number');
|
|
59
|
+
expect(typeof result.height).toBe('number');
|
|
60
|
+
expect(result.width).toBeGreaterThan(0);
|
|
61
|
+
expect(result.height).toBeGreaterThan(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('dpr is a number between 1 and 2', async function () {
|
|
65
|
+
var result = await page.evaluate(function () {
|
|
66
|
+
return punter.dpr;
|
|
67
|
+
});
|
|
68
|
+
expect(typeof result).toBe('number');
|
|
69
|
+
expect(result).toBeGreaterThanOrEqual(1);
|
|
70
|
+
expect(result).toBeLessThanOrEqual(2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('frame is a non-negative number', async function () {
|
|
74
|
+
var result = await page.evaluate(function () {
|
|
75
|
+
return punter.frame;
|
|
76
|
+
});
|
|
77
|
+
expect(typeof result).toBe('number');
|
|
78
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('totalFrames is a non-negative number', async function () {
|
|
82
|
+
var result = await page.evaluate(function () {
|
|
83
|
+
return punter.totalFrames;
|
|
84
|
+
});
|
|
85
|
+
expect(typeof result).toBe('number');
|
|
86
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('sprites returns an array', async function () {
|
|
90
|
+
var result = await page.evaluate(function () {
|
|
91
|
+
return Array.isArray(punter.sprites);
|
|
92
|
+
});
|
|
93
|
+
expect(result).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('orientation is "portrait" or "landscape"', async function () {
|
|
97
|
+
var result = await page.evaluate(function () {
|
|
98
|
+
return punter.orientation;
|
|
99
|
+
});
|
|
100
|
+
expect(['portrait', 'landscape']).toContain(result);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('isMobile and isDesktop are booleans and mutually exclusive', async function () {
|
|
104
|
+
var result = await page.evaluate(function () {
|
|
105
|
+
return { isMobile: punter.isMobile, isDesktop: punter.isDesktop };
|
|
106
|
+
});
|
|
107
|
+
expect(typeof result.isMobile).toBe('boolean');
|
|
108
|
+
expect(typeof result.isDesktop).toBe('boolean');
|
|
109
|
+
expect(result.isMobile).not.toBe(result.isDesktop);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('sceneName is a string', async function () {
|
|
113
|
+
var result = await page.evaluate(function () {
|
|
114
|
+
return typeof punter.sceneName;
|
|
115
|
+
});
|
|
116
|
+
expect(result).toBe('string');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// --- debug flag ---
|
|
120
|
+
|
|
121
|
+
it('debug can be toggled on and off', async function () {
|
|
122
|
+
var result = await page.evaluate(function () {
|
|
123
|
+
punter.debug = true;
|
|
124
|
+
var on = punter.debug;
|
|
125
|
+
punter.debug = false;
|
|
126
|
+
return { on: on, off: punter.debug };
|
|
127
|
+
});
|
|
128
|
+
expect(result.on).toBe(true);
|
|
129
|
+
expect(result.off).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('debug only accepts boolean true; other values are treated as false', async function () {
|
|
133
|
+
var result = await page.evaluate(function () {
|
|
134
|
+
punter.debug = 'yes';
|
|
135
|
+
var afterString = punter.debug;
|
|
136
|
+
punter.debug = 1;
|
|
137
|
+
var afterNumber = punter.debug;
|
|
138
|
+
punter.debug = false;
|
|
139
|
+
return { afterString: afterString, afterNumber: afterNumber };
|
|
140
|
+
});
|
|
141
|
+
expect(result.afterString).toBe(false);
|
|
142
|
+
expect(result.afterNumber).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// --- event handlers ---
|
|
146
|
+
|
|
147
|
+
it('on() registers a handler for known events without throwing', async function () {
|
|
148
|
+
var threw = await page.evaluate(function () {
|
|
149
|
+
var events = ['ready', 'update', 'draw', 'resize', 'go'];
|
|
150
|
+
try {
|
|
151
|
+
events.forEach(function (e) { punter.on(e, function () {}); });
|
|
152
|
+
return false;
|
|
153
|
+
} catch (e) { return true; }
|
|
154
|
+
});
|
|
155
|
+
expect(threw).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('on() throws for an unknown event name', async function () {
|
|
159
|
+
var threw = await page.evaluate(function () {
|
|
160
|
+
try { punter.on('unknownEvent', function () {}); return false; }
|
|
161
|
+
catch (e) { return true; }
|
|
162
|
+
});
|
|
163
|
+
expect(threw).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// --- pause / resume ---
|
|
167
|
+
|
|
168
|
+
it('pause() sets paused to true and running to false', async function () {
|
|
169
|
+
var result = await page.evaluate(function () {
|
|
170
|
+
punter.scene('pauseScene', function () {});
|
|
171
|
+
punter.go('pauseScene');
|
|
172
|
+
punter.pause();
|
|
173
|
+
return { paused: punter.paused, running: punter.running };
|
|
174
|
+
});
|
|
175
|
+
expect(result.paused).toBe(true);
|
|
176
|
+
expect(result.running).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('resume() sets running to true and paused to false', async function () {
|
|
180
|
+
var result = await page.evaluate(function () {
|
|
181
|
+
punter.scene('resumeScene', function () {});
|
|
182
|
+
punter.go('resumeScene');
|
|
183
|
+
punter.pause();
|
|
184
|
+
punter.resume();
|
|
185
|
+
return { paused: punter.paused, running: punter.running };
|
|
186
|
+
});
|
|
187
|
+
expect(result.running).toBe(true);
|
|
188
|
+
expect(result.paused).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// --- redraw ---
|
|
192
|
+
|
|
193
|
+
it('redraw() does not throw', async function () {
|
|
194
|
+
var threw = await page.evaluate(function () {
|
|
195
|
+
try { punter.redraw(); return false; }
|
|
196
|
+
catch (e) { return true; }
|
|
197
|
+
});
|
|
198
|
+
expect(threw).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spec_dir": "tests",
|
|
3
|
+
"spec_files": [
|
|
4
|
+
"**/*[sS]pec.?(m)js",
|
|
5
|
+
"!helpers/**",
|
|
6
|
+
"!fixtures/**"
|
|
7
|
+
],
|
|
8
|
+
"helpers": [
|
|
9
|
+
"jasmine/teardown.js",
|
|
10
|
+
"jasmine/reporter.js",
|
|
11
|
+
"jasmine/timeout.js"
|
|
12
|
+
],
|
|
13
|
+
"env": {
|
|
14
|
+
"stopSpecOnExpectationFailure": false,
|
|
15
|
+
"random": false,
|
|
16
|
+
"failFast": true,
|
|
17
|
+
"stopOnSpecFailure": true,
|
|
18
|
+
"hideDisabled": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
var JasmineConsoleReporter = require('jasmine-console-reporter');
|
|
2
|
+
|
|
3
|
+
var jasmineEnv = jasmine.getEnv();
|
|
4
|
+
var reporter = new JasmineConsoleReporter({
|
|
5
|
+
colors: 1, // (0|false)|(1|true)|2
|
|
6
|
+
cleanStack: 1, // (0|false)|(1|true)|2|3
|
|
7
|
+
verbosity: 4, // (0|false)|1|2|(3|true)|4|Object
|
|
8
|
+
|
|
9
|
+
// verbosity: {
|
|
10
|
+
// specs: true,
|
|
11
|
+
// failed: true,
|
|
12
|
+
// pending: true,
|
|
13
|
+
// disabled: true,
|
|
14
|
+
// summary: true
|
|
15
|
+
// },
|
|
16
|
+
|
|
17
|
+
listStyle: 'indent', // "flat"|"indent"
|
|
18
|
+
timeUnit: 'ms', // "ms"|"ns"|"s"
|
|
19
|
+
timeThreshold: { ok: 500, warn: 1000, ouch: 3000 }, // Object|Number
|
|
20
|
+
activity: false, // boolean or string ("dots"|"star"|"flip"|"bouncingBar"|...)
|
|
21
|
+
emoji: true
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
jasmineEnv.clearReporters();
|
|
25
|
+
jasmineEnv.addReporter(reporter);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000; // 2 minute
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var setup = require('./setup');
|
|
4
|
+
|
|
5
|
+
describe('Scenes', function () {
|
|
6
|
+
|
|
7
|
+
var page;
|
|
8
|
+
|
|
9
|
+
beforeAll(async function () {
|
|
10
|
+
page = await setup.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async function () {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('registers a scene without throwing', async function () {
|
|
18
|
+
var threw = await page.evaluate(function () {
|
|
19
|
+
try { punter.scene('menu', function () {}); return false; }
|
|
20
|
+
catch (e) { return true; }
|
|
21
|
+
});
|
|
22
|
+
expect(threw).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('go() calls the registered scene handler', async function () {
|
|
26
|
+
var result = await page.evaluate(function () {
|
|
27
|
+
var called = false;
|
|
28
|
+
punter.scene('testScene', function () { called = true; });
|
|
29
|
+
punter.go('testScene');
|
|
30
|
+
return called;
|
|
31
|
+
});
|
|
32
|
+
expect(result).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('sceneName reflects the active scene after go()', async function () {
|
|
36
|
+
var result = await page.evaluate(function () {
|
|
37
|
+
punter.scene('levelA', function () {});
|
|
38
|
+
punter.go('levelA');
|
|
39
|
+
return punter.sceneName;
|
|
40
|
+
});
|
|
41
|
+
expect(result).toBe('levelA');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('go() switches from one scene to another', async function () {
|
|
45
|
+
var result = await page.evaluate(function () {
|
|
46
|
+
punter.scene('sceneOne', function () {});
|
|
47
|
+
punter.scene('sceneTwo', function () {});
|
|
48
|
+
punter.go('sceneOne');
|
|
49
|
+
var first = punter.sceneName;
|
|
50
|
+
punter.go('sceneTwo');
|
|
51
|
+
return { first: first, second: punter.sceneName };
|
|
52
|
+
});
|
|
53
|
+
expect(result.first).toBe('sceneOne');
|
|
54
|
+
expect(result.second).toBe('sceneTwo');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('go() throws for an unregistered scene name', async function () {
|
|
58
|
+
var threw = await page.evaluate(function () {
|
|
59
|
+
try { punter.go('doesNotExist'); return false; }
|
|
60
|
+
catch (e) { return true; }
|
|
61
|
+
});
|
|
62
|
+
expect(threw).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('go() clears input from the previous scene', async function () {
|
|
66
|
+
var result = await page.evaluate(function () {
|
|
67
|
+
punter.keys['ArrowLeft'] = true;
|
|
68
|
+
punter.mouse.clicked = true;
|
|
69
|
+
punter.scene('afterClear', function () {});
|
|
70
|
+
punter.go('afterClear');
|
|
71
|
+
return { key: punter.keys['ArrowLeft'], clicked: punter.mouse.clicked };
|
|
72
|
+
});
|
|
73
|
+
expect(result.key).toBe(false);
|
|
74
|
+
expect(result.clicked).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('registering a scene with the same name overwrites the previous handler', async function () {
|
|
78
|
+
var result = await page.evaluate(function () {
|
|
79
|
+
var calls = [];
|
|
80
|
+
punter.scene('reused', function () { calls.push('first'); });
|
|
81
|
+
punter.scene('reused', function () { calls.push('second'); });
|
|
82
|
+
punter.go('reused');
|
|
83
|
+
return calls;
|
|
84
|
+
});
|
|
85
|
+
expect(result).toEqual(['second']);
|
|
86
|
+
});
|
|
87
|
+
});
|
package/tests/setup.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var http = require('http');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var puppeteer = require('puppeteer');
|
|
7
|
+
|
|
8
|
+
var ROOT = path.resolve(__dirname, '..');
|
|
9
|
+
var MIME = { '.html': 'text/html', '.js': 'application/javascript', '.wav': 'audio/wav', '.png': 'image/png' };
|
|
10
|
+
|
|
11
|
+
var _server, _browser, _baseUrl;
|
|
12
|
+
var _started = null; // promise to ensure single init
|
|
13
|
+
|
|
14
|
+
function serve(req, res) {
|
|
15
|
+
var filePath = path.join(ROOT, decodeURIComponent(req.url));
|
|
16
|
+
fs.readFile(filePath, function (err, data) {
|
|
17
|
+
if (err) { res.writeHead(404); res.end(); return; }
|
|
18
|
+
res.writeHead(200, { 'Content-Type': MIME[path.extname(filePath)] || 'application/octet-stream' });
|
|
19
|
+
res.end(data);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ensureStarted() {
|
|
24
|
+
if (_started) return _started;
|
|
25
|
+
_started = (async function () {
|
|
26
|
+
await new Promise(function (resolve) {
|
|
27
|
+
_server = http.createServer(serve);
|
|
28
|
+
_server.listen(0, '127.0.0.1', resolve);
|
|
29
|
+
});
|
|
30
|
+
_baseUrl = 'http://127.0.0.1:' + _server.address().port;
|
|
31
|
+
_browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--autoplay-policy=no-user-gesture-required'] });
|
|
32
|
+
})();
|
|
33
|
+
return _started;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function newPage() {
|
|
37
|
+
await ensureStarted();
|
|
38
|
+
var page = await _browser.newPage();
|
|
39
|
+
await page.goto(_baseUrl + '/tests/fixtures/index.html', { waitUntil: 'domcontentloaded' });
|
|
40
|
+
await page.waitForFunction('window.__ready === true', { timeout: 5000 });
|
|
41
|
+
return page;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function stop() {
|
|
45
|
+
if (_browser) await _browser.close();
|
|
46
|
+
if (_server) await new Promise(function (r) { _server.close(r); });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { newPage: newPage, stop: stop };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var setup = require('./setup');
|
|
4
|
+
|
|
5
|
+
describe('Sounds', function () {
|
|
6
|
+
|
|
7
|
+
var page;
|
|
8
|
+
|
|
9
|
+
beforeAll(async function () {
|
|
10
|
+
page = await setup.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async function () {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('playSound does not throw for a loaded sound', async function () {
|
|
18
|
+
var threw = await page.evaluate(function () {
|
|
19
|
+
try { punter.playSound('beep'); return false; }
|
|
20
|
+
catch (e) { return true; }
|
|
21
|
+
});
|
|
22
|
+
expect(threw).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('playSound does not throw for an unknown sound key', async function () {
|
|
26
|
+
var threw = await page.evaluate(function () {
|
|
27
|
+
try { punter.playSound('nonExistentSound'); return false; }
|
|
28
|
+
catch (e) { return true; }
|
|
29
|
+
});
|
|
30
|
+
expect(threw).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('playSound respects volume option without throwing', async function () {
|
|
34
|
+
var threw = await page.evaluate(function () {
|
|
35
|
+
try { punter.playSound('beep', { volume: 0.5 }); return false; }
|
|
36
|
+
catch (e) { return true; }
|
|
37
|
+
});
|
|
38
|
+
expect(threw).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('playSound respects loop option without throwing', async function () {
|
|
42
|
+
var threw = await page.evaluate(function () {
|
|
43
|
+
try { punter.playSound('beep', { loop: true }); return false; }
|
|
44
|
+
catch (e) { return true; }
|
|
45
|
+
});
|
|
46
|
+
expect(threw).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('playSound respects speed option without throwing', async function () {
|
|
50
|
+
var threw = await page.evaluate(function () {
|
|
51
|
+
try { punter.playSound('beep', { speed: 1.5 }); return false; }
|
|
52
|
+
catch (e) { return true; }
|
|
53
|
+
});
|
|
54
|
+
expect(threw).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('stopSound does not throw when the sound is playing', async function () {
|
|
58
|
+
var threw = await page.evaluate(function () {
|
|
59
|
+
try {
|
|
60
|
+
punter.playSound('beep');
|
|
61
|
+
punter.stopSound('beep');
|
|
62
|
+
return false;
|
|
63
|
+
} catch (e) { return true; }
|
|
64
|
+
});
|
|
65
|
+
expect(threw).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('stopSound does not throw when nothing is playing', async function () {
|
|
69
|
+
var threw = await page.evaluate(function () {
|
|
70
|
+
try { punter.stopSound('beep'); return false; }
|
|
71
|
+
catch (e) { return true; }
|
|
72
|
+
});
|
|
73
|
+
expect(threw).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('stopSound does not throw for an unknown sound key', async function () {
|
|
77
|
+
var threw = await page.evaluate(function () {
|
|
78
|
+
try { punter.stopSound('nonExistentSound'); return false; }
|
|
79
|
+
catch (e) { return true; }
|
|
80
|
+
});
|
|
81
|
+
expect(threw).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|