Skip to content
This repository was archived by the owner on Dec 2, 2024. It is now read-only.
/ level-js Public archive

Commit e62af6f

Browse files
authored
Test all types of the structured clone algorithm (#101)
* stringify test doesn't require binary key support * catch DataCloneError if env does not support serializing key or value * test all types of the structured clone algorithm
1 parent 12e9294 commit e62af6f

File tree

5 files changed

+229
-34
lines changed

5 files changed

+229
-34
lines changed

index.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
66
var util = require('util')
77
var Iterator = require('./iterator')
88
var mixedToBuffer = require('./util/mixed-to-buffer')
9+
var isDataCloneError = require('./util/is-data-clone-error')
910
var setImmediate = require('./util/immediate')
1011
var support = require('./util/support')
1112

@@ -89,7 +90,23 @@ Level.prototype._del = function(key, options, callback) {
8990
}
9091

9192
Level.prototype._put = function (key, value, options, callback) {
92-
this.await(this.store('readwrite').put(value, key), callback)
93+
var store = this.store('readwrite')
94+
95+
try {
96+
// Will throw a DataCloneError if the environment
97+
// does not support serializing the key or value.
98+
var req = store.put(value, key)
99+
} catch (err) {
100+
if (!isDataCloneError(err)) {
101+
throw err
102+
}
103+
104+
return setImmediate(function () {
105+
callback(err)
106+
})
107+
}
108+
109+
this.await(req, callback)
93110
}
94111

95112
// Valid key types in IndexedDB Second Edition:

test/custom-test.js

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ module.exports = function (leveljs, test, testCommon) {
6262
})
6363

6464
// Adapted from a memdown test.
65-
leveljs.binaryKeys && test('iterator stringifies buffer input', function (t) {
65+
test('iterator stringifies buffer input', function (t) {
6666
t.plan(6)
6767

6868
var db = leveljs(testCommon.location())
@@ -86,38 +86,6 @@ module.exports = function (leveljs, test, testCommon) {
8686
})
8787
})
8888

89-
// TODO: merge this and the test below. Test all types.
90-
test('store native JS types', function(t) {
91-
var level = leveljs(testCommon.location())
92-
level.open(function(err) {
93-
t.notOk(err, 'no error')
94-
level.put('key', true, function (err) {
95-
t.notOk(err, 'no error')
96-
level.get('key', { asBuffer: false }, function(err, value) {
97-
t.notOk(err, 'no error')
98-
t.ok(typeof value === 'boolean', 'is boolean type')
99-
t.ok(value, 'is truthy')
100-
level.close(t.end.bind(t))
101-
})
102-
})
103-
})
104-
})
105-
106-
test('store NaN value', function(t) {
107-
var level = leveljs(testCommon.location())
108-
level.open(function(err) {
109-
t.notOk(err, 'no error')
110-
level.put('key', NaN, function (err) {
111-
t.notOk(err, 'no error')
112-
level.get('key', { asBuffer: false }, function(err, value) {
113-
t.notOk(err, 'no error')
114-
t.ok(typeof value === 'number' && isNaN(value), 'is NaN')
115-
level.close(t.end.bind(t))
116-
})
117-
})
118-
})
119-
})
120-
12189
// NOTE: in chrome (at least) indexeddb gets buggy if you try and destroy a db,
12290
// then create it again, then try and destroy it again. these avoid doing that
12391

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ require('abstract-leveldown/abstract/iterator-range-test').all(leveljs, test, te
3030

3131
// Additional tests for this implementation
3232
require('./custom-test')(leveljs, test, testCommon)
33+
require('./structured-clone-test')(leveljs, test, testCommon)
3334
require('./levelup-test')(leveljs, test, testCommon)

test/structured-clone-test.js

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
'use strict'
2+
3+
var levelup = require('levelup')
4+
var isDataCloneError = require('../util/is-data-clone-error')
5+
var bytes = [0, 127]
6+
7+
// Replacement for TypedArray.from(bytes)
8+
function ta (TypedArray) {
9+
var arr = new TypedArray(bytes.length)
10+
for (var i = 0; i < bytes.length; i++) arr[i] = bytes[i]
11+
return arr
12+
}
13+
14+
// level-js supports all types of the structured clone algorithm
15+
// except for null and undefined (unless nested in another type).
16+
var types = [
17+
{ type: 'boolean', value: true },
18+
{ type: 'number', value: -20 },
19+
{
20+
type: 'NaN',
21+
value: NaN,
22+
test: function (value) {
23+
// Replacement for Number.isNaN (for IE <= 11)
24+
return typeof value === 'number' && isNaN(value)
25+
}
26+
},
27+
{ type: '+Infinity', value: Infinity },
28+
{ type: '-Infinity', value: -Infinity },
29+
{ type: 'string', value: 'test' },
30+
{ type: 'Boolean object', value: new Boolean(false) },
31+
{ type: 'String object', value: new String('test') },
32+
{ type: 'Date', ctor: true, value: new Date() },
33+
{ type: 'RegExp', ctor: true, value: /r/g },
34+
{ type: 'Array', ctor: true, value: [0, null, undefined] },
35+
{ type: 'Object', ctor: true, value: { a: null, b: [undefined] } },
36+
{
37+
type: 'Object',
38+
name: 'Object (null prototype)',
39+
ctor: true,
40+
createValue: function () {
41+
return Object.create(null)
42+
}
43+
},
44+
45+
{ type: 'ArrayBuffer', ctor: true, allowFailure: true, value: ta(Buffer).buffer },
46+
{ type: 'Int8Array', ctor: true, allowFailure: true, createValue: ta },
47+
48+
// Don't allow failure as this is the primary type for binary (Buffer) data
49+
{ type: 'Uint8Array', ctor: true, createValue: ta },
50+
51+
{ type: 'Uint8ClampedArray', ctor: true, allowFailure: true, createValue: ta },
52+
{ type: 'Int16Array', ctor: true, allowFailure: true, createValue: ta },
53+
{ type: 'Uint16Array', ctor: true, allowFailure: true, createValue: ta },
54+
{ type: 'Int32Array', ctor: true, allowFailure: true, createValue: ta },
55+
{ type: 'Uint32Array', ctor: true, allowFailure: true, createValue: ta },
56+
{ type: 'Float32Array', ctor: true, allowFailure: true, createValue: ta },
57+
{ type: 'Float64Array', ctor: true, allowFailure: true, createValue: ta },
58+
{
59+
type: 'Map',
60+
ctor: true,
61+
allowFailure: true,
62+
createValue: function (ctor) {
63+
// Replacement for Map constructor arguments (for IE 11)
64+
var value = new ctor()
65+
value.set('test', 123)
66+
return value
67+
},
68+
test: function (value) {
69+
return value.get('test') === 123
70+
}
71+
},
72+
{
73+
type: 'Set',
74+
ctor: true,
75+
allowFailure: true,
76+
createValue: function (ctor) {
77+
// Replacement for Set constructor arguments (for IE 11)
78+
var value = new ctor()
79+
value.add(123)
80+
return value
81+
},
82+
test: function (value) {
83+
return value.has(123)
84+
}
85+
},
86+
{
87+
type: 'Blob',
88+
ctor: true,
89+
allowFailure: true,
90+
createValue: function (ctor) {
91+
return new ctor(['test'])
92+
},
93+
test: function (value) {
94+
// TODO. This test would be asynchronous.
95+
return true
96+
}
97+
},
98+
{
99+
type: 'File',
100+
ctor: true,
101+
allowFailure: true,
102+
createValue: function (ctor) {
103+
return new ctor(['test'], 'filename')
104+
},
105+
test: function (value) {
106+
// TODO. This test would be asynchronous.
107+
return true
108+
}
109+
},
110+
{
111+
type: 'FileList',
112+
ctor: true,
113+
allowFailure: true,
114+
createValue: function () {
115+
var input = global.document.createElement('input')
116+
input.type = 'file'
117+
return input.files
118+
}
119+
},
120+
{
121+
type: 'ImageData',
122+
ctor: true,
123+
allowFailure: true,
124+
createValue: function (ctor) {
125+
return new ctor(1, 1)
126+
},
127+
test: function (value) {
128+
return value.data.length === 4
129+
}
130+
}
131+
]
132+
133+
module.exports = function (leveljs, test, testCommon) {
134+
var db
135+
136+
test('setUp', testCommon.setUp)
137+
test('open', function (t) {
138+
db = leveljs(testCommon.location())
139+
db.open(t.end.bind(t))
140+
})
141+
142+
types.forEach(function (item) {
143+
var testName = item.name || item.type
144+
145+
test('structured clone: ' + testName, function (t) {
146+
var ctor = item.ctor ? global[item.type] : null
147+
var skip = item.allowFailure ? 'pass' : 'fail'
148+
var input = item.value
149+
150+
if (item.ctor && !ctor) {
151+
t[skip]('constructor is undefined in this environment')
152+
return t.end()
153+
}
154+
155+
if (item.createValue) {
156+
try {
157+
input = item.createValue(ctor)
158+
} catch (err) {
159+
t[skip]('constructor is not spec-compliant in this environment')
160+
return t.end()
161+
}
162+
}
163+
164+
db.put(testName, input, function (err) {
165+
if (err && isDataCloneError(err)) {
166+
t[skip]('serializing is not supported by the structured clone algorithm of this environment')
167+
return t.end()
168+
}
169+
170+
t.notOk(err, 'no put error')
171+
172+
db.get(testName, { asBuffer: false }, function (err, value) {
173+
t.notOk(err, 'no get error')
174+
175+
if (ctor) {
176+
var expected = '[object ' + item.type + ']'
177+
var actual = Object.prototype.toString.call(value)
178+
179+
if (actual === expected) {
180+
t.is(actual, expected, 'prototype')
181+
t.ok(value instanceof ctor, 'instanceof')
182+
} else {
183+
t[skip]('deserializing is not supported by the structured clone algorithm of this environment')
184+
return t.end()
185+
}
186+
}
187+
188+
if (item.test) {
189+
t.ok(item.test(value), 'correct value')
190+
} else {
191+
t.same(value, input, 'correct value')
192+
}
193+
194+
t.end()
195+
})
196+
})
197+
})
198+
})
199+
200+
test('close', function (t) { db.close(t.end.bind(t)) })
201+
test('teardown', testCommon.tearDown)
202+
}

util/is-data-clone-error.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
3+
var LEGACY_CODE = 25
4+
5+
module.exports = function (err) {
6+
return err.name === 'DataCloneError' || err.code === LEGACY_CODE
7+
}

0 commit comments

Comments
 (0)