forked from izu/student-web-if-development-kit
fix(tapops): deteksi format file dan format tag
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { DirectusClient } from '../js/services/directus-client.js';
|
||||
|
||||
test('requests berkas with expanded Directus file metadata', () => {
|
||||
const url = new URL(DirectusClient.itemsUrl('berkas'));
|
||||
|
||||
assert.equal(url.pathname, '/items/berkas');
|
||||
assert.equal(url.searchParams.get('limit'), '-1');
|
||||
assert.equal(url.searchParams.get('fields'), '*,file.id,file.filename_download,file.type,file.filename_disk');
|
||||
});
|
||||
|
||||
test('builds asset and viewer URLs from expanded Directus file objects', () => {
|
||||
const item = {
|
||||
nama_berkas: 'Panduan Penulisan Proposal Kerja Praktek',
|
||||
file: {
|
||||
id: '4976330e-e547-45f6-b9be-afce493c8a82',
|
||||
filename_download: 'Panduan Penulisan Proposal Kerja Praktek.docx',
|
||||
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
DirectusClient.assetUrl(item.file),
|
||||
'https://api.ifuntanhub.dev/assets/4976330e-e547-45f6-b9be-afce493c8a82',
|
||||
);
|
||||
|
||||
const viewerUrl = new URL(DirectusClient.viewerUrl(item), 'https://example.test/groups/Tapops/');
|
||||
assert.equal(viewerUrl.pathname, '/groups/Tapops/viewer.html');
|
||||
assert.equal(viewerUrl.searchParams.get('id'), '4976330e-e547-45f6-b9be-afce493c8a82');
|
||||
assert.equal(viewerUrl.searchParams.get('name'), 'Panduan Penulisan Proposal Kerja Praktek');
|
||||
assert.equal(viewerUrl.searchParams.get('type'), 'docx');
|
||||
});
|
||||
@@ -15,6 +15,14 @@ test('parses comma-separated and JSON string tags', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('formats tag labels without breaking abbreviations', () => {
|
||||
assert.equal(DocumentFormatter.formatTag('SOP'), 'SOP');
|
||||
assert.equal(DocumentFormatter.formatTag('kp'), 'KP');
|
||||
assert.equal(DocumentFormatter.formatTag('kerja praktek'), 'Kerja Praktek');
|
||||
assert.equal(DocumentFormatter.formatTag('Kerja Praktek'), 'Kerja Praktek');
|
||||
assert.equal(DocumentFormatter.formatTag('umum'), 'Umum');
|
||||
});
|
||||
|
||||
test('maps parsed tags to fixed repository categories', () => {
|
||||
assert.equal(DocumentFormatter.categoryFromTags(['TA']), 'skripsi');
|
||||
assert.equal(DocumentFormatter.categoryFromTags(['Kalender']), 'akademik');
|
||||
@@ -26,6 +34,78 @@ test('maps parsed tags to fixed repository categories', () => {
|
||||
test('derives file type and safe download filename from document name', () => {
|
||||
assert.equal(DocumentFormatter.fileTypeFromName('Panduan Akademik.pdf'), 'pdf');
|
||||
assert.equal(DocumentFormatter.fileTypeFromName('Form KP.DOCX'), 'docx');
|
||||
assert.equal(DocumentFormatter.fileTypeFromName('Catatan.txt'), 'txt');
|
||||
assert.equal(DocumentFormatter.fileTypeFromName('Materi Seminar.pptx'), 'pptx');
|
||||
assert.equal(DocumentFormatter.downloadFilename('Form KP', 'docx'), 'Form KP.docx');
|
||||
assert.equal(DocumentFormatter.downloadFilename('Template lama.DOC', 'doc'), 'Template lama.DOC');
|
||||
assert.equal(DocumentFormatter.downloadFilename('Kalender.pdf', 'pdf'), 'Kalender.pdf');
|
||||
});
|
||||
|
||||
test('maps supported file names to display metadata for cards', () => {
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromName('Panduan Akademik.pdf'), {
|
||||
type: 'pdf',
|
||||
icon: 'fas fa-file-pdf',
|
||||
css: 'doc-type-pdf',
|
||||
});
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromName('Form KP.DOCX'), {
|
||||
type: 'docx',
|
||||
icon: 'fas fa-file-word',
|
||||
css: 'doc-type-docx',
|
||||
});
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromName('Catatan.txt'), {
|
||||
type: 'txt',
|
||||
icon: 'fas fa-file-alt',
|
||||
css: 'doc-type-txt',
|
||||
});
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromName('Materi Seminar.pptx'), {
|
||||
type: 'pptx',
|
||||
icon: 'fas fa-file-powerpoint',
|
||||
css: 'doc-type-pptx',
|
||||
});
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromName('Lampiran.7z'), {
|
||||
type: '7z',
|
||||
icon: 'fas fa-file-archive',
|
||||
css: 'doc-type-archive',
|
||||
});
|
||||
});
|
||||
|
||||
test('derives file metadata from expanded Directus file records when display title has no extension', () => {
|
||||
const pdfItem = {
|
||||
nama_berkas: 'SOP Kerja Praktek',
|
||||
file: {
|
||||
filename_download: 'SOP Kerja Praktek.pdf',
|
||||
type: 'application/pdf',
|
||||
},
|
||||
};
|
||||
const docxItem = {
|
||||
nama_berkas: 'Panduan Penulisan Proposal Kerja Praktek',
|
||||
file: {
|
||||
filename_download: 'Panduan Penulisan Proposal Kerja Praktek.docx',
|
||||
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromItem(pdfItem), {
|
||||
type: 'pdf',
|
||||
icon: 'fas fa-file-pdf',
|
||||
css: 'doc-type-pdf',
|
||||
});
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromItem(docxItem), {
|
||||
type: 'docx',
|
||||
icon: 'fas fa-file-word',
|
||||
css: 'doc-type-docx',
|
||||
});
|
||||
assert.equal(DocumentFormatter.downloadFilenameFromItem(pdfItem), 'SOP Kerja Praktek.pdf');
|
||||
assert.equal(DocumentFormatter.downloadFilenameFromItem(docxItem), 'Panduan Penulisan Proposal Kerja Praktek.docx');
|
||||
});
|
||||
|
||||
test('falls back to MIME type when Directus filename metadata has no extension', () => {
|
||||
assert.deepEqual(DocumentFormatter.fileInfoFromItem({
|
||||
nama_berkas: 'Berkas Presentasi',
|
||||
file: { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' },
|
||||
}), {
|
||||
type: 'pptx',
|
||||
icon: 'fas fa-file-powerpoint',
|
||||
css: 'doc-type-pptx',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { FileDownloader } from '../js/services/file-downloader.js';
|
||||
import { FileViewer } from '../js/viewer/file-viewer.js';
|
||||
|
||||
class FakeElement {
|
||||
constructor(tagName = 'div') {
|
||||
this.tagName = tagName.toUpperCase();
|
||||
this.children = [];
|
||||
this.attributes = new Map();
|
||||
this.dataset = {};
|
||||
this.eventListeners = new Map();
|
||||
this.style = {};
|
||||
this.className = '';
|
||||
this.href = '';
|
||||
this.src = '';
|
||||
this.title = '';
|
||||
this.textContent = '';
|
||||
this.innerHTML = '';
|
||||
this.download = '';
|
||||
}
|
||||
|
||||
append(...nodes) {
|
||||
this.children.push(...nodes);
|
||||
}
|
||||
|
||||
appendChild(node) {
|
||||
this.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
prepend(node) {
|
||||
this.children.unshift(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
removeChild(node) {
|
||||
this.children = this.children.filter(child => child !== node);
|
||||
return node;
|
||||
}
|
||||
|
||||
replaceChildren(...nodes) {
|
||||
this.children = [...nodes];
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.removed = true;
|
||||
}
|
||||
|
||||
setAttribute(name, value) {
|
||||
this.attributes.set(name, String(value));
|
||||
}
|
||||
|
||||
getAttribute(name) {
|
||||
return this.attributes.get(name);
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
this.attributes.delete(name);
|
||||
}
|
||||
|
||||
addEventListener(type, handler) {
|
||||
this.eventListeners.set(type, handler);
|
||||
}
|
||||
|
||||
click() {
|
||||
this.clicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function installViewerDom(search) {
|
||||
const previous = {
|
||||
document: globalThis.document,
|
||||
window: globalThis.window,
|
||||
URL: globalThis.URL,
|
||||
fetch: globalThis.fetch,
|
||||
setTimeout: globalThis.setTimeout,
|
||||
consoleError: console.error,
|
||||
consoleWarn: console.warn,
|
||||
};
|
||||
const elements = {
|
||||
title: new FakeElement('h1'),
|
||||
type: new FakeElement('span'),
|
||||
download: new FakeElement('a'),
|
||||
content: new FakeElement('main'),
|
||||
};
|
||||
const body = new FakeElement('body');
|
||||
const created = [];
|
||||
|
||||
globalThis.document = {
|
||||
title: '',
|
||||
body,
|
||||
getElementById(id) {
|
||||
return {
|
||||
'viewer-title': elements.title,
|
||||
'viewer-type': elements.type,
|
||||
'viewer-download': elements.download,
|
||||
'viewer-content': elements.content,
|
||||
}[id];
|
||||
},
|
||||
createElement(tagName) {
|
||||
const node = new FakeElement(tagName);
|
||||
created.push(node);
|
||||
return node;
|
||||
},
|
||||
};
|
||||
globalThis.window = {
|
||||
location: { search },
|
||||
open(url) {
|
||||
this.openedUrl = url;
|
||||
},
|
||||
};
|
||||
globalThis.URL = {
|
||||
createObjectURL() {
|
||||
return 'blob:tapops-download';
|
||||
},
|
||||
revokeObjectURL(url) {
|
||||
this.revokedUrl = url;
|
||||
},
|
||||
};
|
||||
globalThis.setTimeout = callback => {
|
||||
callback();
|
||||
return 1;
|
||||
};
|
||||
console.error = () => {};
|
||||
console.warn = () => {};
|
||||
|
||||
return {
|
||||
elements,
|
||||
created,
|
||||
restore() {
|
||||
globalThis.document = previous.document;
|
||||
globalThis.window = previous.window;
|
||||
globalThis.URL = previous.URL;
|
||||
globalThis.fetch = previous.fetch;
|
||||
globalThis.setTimeout = previous.setTimeout;
|
||||
console.error = previous.consoleError;
|
||||
console.warn = previous.consoleWarn;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('repository download fetches the Directus original-file URL', async (t) => {
|
||||
const { created, restore } = installViewerDom('');
|
||||
t.after(restore);
|
||||
|
||||
let fetchedUrl = '';
|
||||
globalThis.fetch = async (url) => {
|
||||
fetchedUrl = url;
|
||||
return {
|
||||
ok: true,
|
||||
blob: async () => new Blob(['docx']),
|
||||
};
|
||||
};
|
||||
|
||||
await FileDownloader.download('https://api.ifuntanhub.dev/assets/docx-id', 'Form KP.docx');
|
||||
|
||||
const clickedAnchor = created.find(node => node.tagName === 'A' && node.clicked);
|
||||
assert.equal(fetchedUrl, 'https://api.ifuntanhub.dev/assets/docx-id?download');
|
||||
assert.equal(clickedAnchor.download, 'Form KP.docx');
|
||||
});
|
||||
|
||||
test('DOCX preview renders Microsoft Office Online Viewer iframe', async (t) => {
|
||||
const { elements, restore } = installViewerDom('?id=docx-id&name=Form%20KP.docx&type=docx');
|
||||
t.after(restore);
|
||||
|
||||
await new FileViewer().init();
|
||||
|
||||
const iframe = elements.content.children[0];
|
||||
const assetUrl = 'https://api.ifuntanhub.dev/assets/docx-id';
|
||||
assert.equal(iframe.tagName, 'IFRAME');
|
||||
assert.equal(iframe.className, 'viewer-office-frame');
|
||||
assert.equal(iframe.src, `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(assetUrl)}`);
|
||||
});
|
||||
|
||||
test('viewer download uses the Directus original-file URL', async (t) => {
|
||||
const { elements, created, restore } = installViewerDom('?id=pdf-id&name=Panduan.pdf&type=pdf');
|
||||
t.after(restore);
|
||||
|
||||
let fetchedUrl = '';
|
||||
globalThis.fetch = async (url) => {
|
||||
fetchedUrl = url;
|
||||
return {
|
||||
ok: true,
|
||||
headers: { get: () => null },
|
||||
blob: async () => new Blob(['pdf']),
|
||||
};
|
||||
};
|
||||
|
||||
await new FileViewer().init();
|
||||
await elements.download.eventListeners.get('click')({ preventDefault() {} });
|
||||
|
||||
const clickedAnchor = created.find(node => node.tagName === 'A' && node.clicked);
|
||||
assert.equal(fetchedUrl, 'https://api.ifuntanhub.dev/assets/pdf-id?download');
|
||||
assert.equal(clickedAnchor.download, 'Panduan.pdf');
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs';
|
||||
|
||||
const indexHtml = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
const viewerHtml = readFileSync(new URL('../viewer.html', import.meta.url), 'utf8');
|
||||
const repositoryCardsCss = readFileSync(new URL('../css/repository/cards.css', import.meta.url), 'utf8');
|
||||
|
||||
test('Tapops index uses module scripts and no inline behavior', () => {
|
||||
assert.match(indexHtml, /<script type="module" src="js\/app\.js"><\/script>/);
|
||||
@@ -15,3 +16,7 @@ test('Tapops viewer uses module scripts and no inline behavior', () => {
|
||||
assert.match(viewerHtml, /<script type="module" src="js\/viewer\/viewer-app\.js"><\/script>/);
|
||||
assert.doesNotMatch(viewerHtml, /<script>\s*\(/);
|
||||
});
|
||||
|
||||
test('Tapops document tags keep the API-provided casing', () => {
|
||||
assert.doesNotMatch(repositoryCardsCss, /\.doc-tag\s*\{[^}]*text-transform:\s*lowercase/is);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user