Add files via upload
This commit is contained in:
18 files changed
+12656
No files matched your search
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const root = path.resolve(__dirname,'..');
|
||||
global.flipnote=vm.compileFunction(fs.readFileSync(path.join(root,'assets/vendor/flipnote.js'),'utf8')+'\nreturn flipnote;',['module'])(module);
|
||||
const importer = require('../assets/ppm-import.js');
|
||||
const fixture = fs.readFileSync(path.join(__dirname,'fixtures/local-import.ppm'));
|
||||
const buffer = bytes => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||
const bit = (encoded,index) => (Buffer.from(encoded,'base64')[index >> 3] >> (index & 7)) & 1;
|
||||
|
||||
test('local import keeps both layers, per-page colours, paper and visibility',()=>{
|
||||
const result=importer.decode(buffer(fixture),'Local.ppm');
|
||||
assert.equal(result.project.frames.length,2);
|
||||
assert.equal(result.project.title,'Local');
|
||||
assert.deepEqual(result.project.layer_visibility,[true,false]);
|
||||
assert.deepEqual(result.project.frames.map(frame=>frame.colours),[['black','red'],['blue','black']]);
|
||||
assert.deepEqual(result.project.frames.map(frame=>frame.paper),['white','black']);
|
||||
assert.equal(bit(result.project.frames[0].layer1,0),1);
|
||||
assert.equal(bit(result.project.frames[0].layer2,191*256+255),1);
|
||||
assert.equal(bit(result.project.frames[1].layer1,7*256+7),1);
|
||||
assert.equal(bit(result.project.frames[1].layer1,0),0);
|
||||
assert.equal(bit(result.project.frames[1].layer2,10*256+10),1);
|
||||
assert.equal(result.project.loop,true);
|
||||
});
|
||||
|
||||
test('audio and page sound triggers stay with an imported animation',()=>{
|
||||
const {project}=importer.decode(buffer(fixture));
|
||||
assert.equal(project.speed_level,6);
|
||||
assert.deepEqual(project.frames[0].sounds,{sound1:true,sound2:false,sound3:false});
|
||||
assert.deepEqual(Object.keys(project.audio),['music','sound1']);
|
||||
for(const track of Object.values(project.audio)){
|
||||
const wav=Buffer.from(track.data,'base64');
|
||||
assert.equal(wav.toString('ascii',0,4),'RIFF');
|
||||
assert.equal(wav.readUInt32LE(24),32768);
|
||||
assert.equal(track.reference_fps,12);
|
||||
assert.equal(track.duration,(wav.length-44)/2/32768);
|
||||
}
|
||||
});
|
||||
|
||||
test('music pitch follows the stored Flipnote and music speeds exactly once',()=>{
|
||||
const normal=importer.decode(buffer(fixture));
|
||||
const fast=Buffer.from(fixture);
|
||||
const animationEnd=0x6A0+fast.readUInt32LE(4);
|
||||
const soundStart=Math.ceil((animationEnd+2)/4)*4;
|
||||
fast[soundStart+16]=1;
|
||||
const faster=importer.decode(buffer(fast));
|
||||
assert.equal(faster.project.speed_level,7);
|
||||
assert.equal(faster.project.audio.music.reference_fps,20);
|
||||
assert.ok(faster.project.audio.music.duration<normal.project.audio.music.duration);
|
||||
assert.equal(faster.project.audio.sound1.duration,normal.project.audio.sound1.duration);
|
||||
});
|
||||
|
||||
test('invalid lengths and out-of-bounds page tables are rejected before decoding',()=>{
|
||||
for(const mutate of [
|
||||
bytes=>bytes.fill(0,0,4),
|
||||
bytes=>bytes.writeUInt32LE(0xFFFFFFFF,4),
|
||||
bytes=>bytes.writeUInt16LE(1000,12),
|
||||
bytes=>bytes.writeUInt16LE(0,0x6A0),
|
||||
bytes=>bytes.writeUInt32LE(0xFFFFFFFF,0x6A8),
|
||||
bytes=>bytes.writeUInt32LE(0xFFFFFFFF,8),
|
||||
]){
|
||||
const bad=Buffer.from(fixture);mutate(bad);
|
||||
assert.throws(()=>importer.decode(buffer(bad)));
|
||||
}
|
||||
assert.throws(()=>importer.decode(buffer(fixture.subarray(0,100))));
|
||||
assert.throws(()=>importer.decode(new ArrayBuffer(1024*1024+1)));
|
||||
});
|
||||
|
||||
test('opening a device file uses its bytes without making a network request',async()=>{
|
||||
const oldFetch=global.fetch;
|
||||
global.fetch=()=>{throw new Error('Unexpected network request');};
|
||||
try{
|
||||
const result=await importer.read({name:'Device.ppm',size:fixture.length,arrayBuffer:async()=>buffer(fixture)});
|
||||
assert.equal(result.project.title,'Device');
|
||||
await assert.rejects(importer.read({name:'not-a-note.txt',size:fixture.length}));
|
||||
await assert.rejects(importer.read({name:'large.ppm',size:1024*1024+1}));
|
||||
}finally{global.fetch=oldFetch;}
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const root = path.resolve(__dirname,'..');
|
||||
const walk = directory => fs.readdirSync(directory,{withFileTypes:true}).flatMap(entry=>
|
||||
entry.isDirectory()?walk(path.join(directory,entry.name)):[path.join(directory,entry.name)]);
|
||||
|
||||
test('the release contains source files and assets, with no server credentials or nested bundles',()=>{
|
||||
for(const file of walk(root)){
|
||||
assert.doesNotMatch(path.relative(root,file),/\.(?:key|pem|sqlite3|zip|xpi|pyc)$/i);
|
||||
if(/\.(?:html|js|css|json|md|txt)$/i.test(file)){
|
||||
assert.doesNotMatch(fs.readFileSync(file,'utf8'),/-----BEGIN (?:RSA )?PRIVATE KEY-----/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('all shipped JavaScript is valid and the editor has no server fetch calls',()=>{
|
||||
for(const file of walk(path.join(root,'assets')).filter(file=>file.endsWith('.js'))){
|
||||
const source=fs.readFileSync(file,'utf8');
|
||||
new vm.Script(source,{filename:file});
|
||||
if(!file.includes(path.join('assets','vendor'))&&!file.endsWith('arabic-text.js')){
|
||||
assert.doesNotMatch(source,/\bfetch\s*\(|XMLHttpRequest|sendBeacon|WebSocket/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('the page keeps local import and video output without site navigation',()=>{
|
||||
const html=fs.readFileSync(path.join(root,'index.html'),'utf8');
|
||||
assert.match(html,/id="flipnote-open-ppm-button"/);
|
||||
assert.match(html,/id="flipnote-audio-file-input"/);
|
||||
assert.match(html,/id="export-btn"/);
|
||||
assert.doesNotMatch(html,/class="site-(?:header|footer)"|<nav\b|action="\//);
|
||||
assert.doesNotMatch(html,/__[A-Z][A-Z0-9_]+__/);
|
||||
});
|
||||
|
||||
test('all literal page images, scripts, styles and font URLs exist locally',()=>{
|
||||
const files=walk(root).filter(file=>/\.(?:html|css|js)$/.test(file)&&!file.includes(path.sep+'vendor'+path.sep));
|
||||
for(const file of files){
|
||||
const text=fs.readFileSync(file,'utf8');
|
||||
const references=[];
|
||||
for(const match of text.matchAll(/(?:src|href)=["'](\.\/[^"']+)["']/g))references.push(match[1]);
|
||||
for(const match of text.matchAll(/url\(["']?(\.\/[^)'"\s]+)["']?\)/g))references.push(match[1]);
|
||||
for(const match of text.matchAll(/["'](\.\/assets\/[^"'$`]+)["']/g))references.push(match[1]);
|
||||
for(const reference of references){
|
||||
const clean=reference.split(/[?#]/)[0];
|
||||
const base=file.endsWith('.css')?path.dirname(file):root;
|
||||
assert.ok(fs.existsSync(path.resolve(base,clean)),path.relative(root,file)+': missing '+clean);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user
