mirror of
https://github.com/edu4rdshl/gnome-shell.git
synced 2026-07-17 23:24:51 +00:00
Merge branch 'extension-utils-test' into 'main'
Add extensionUtils unit test See merge request GNOME/gnome-shell!3379
This commit is contained in:
commit
eb41244c88
15 changed files with 303 additions and 58 deletions
|
|
@ -36,6 +36,7 @@ const SERIALIZED_PROPERTIES = [
|
|||
'hasPrefs',
|
||||
'hasUpdate',
|
||||
'canChange',
|
||||
'sessionModes',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -52,9 +53,7 @@ export function serializeExtension(extension) {
|
|||
obj[prop] = extension[prop];
|
||||
});
|
||||
|
||||
let res = {};
|
||||
for (let key in obj) {
|
||||
let val = obj[key];
|
||||
function packValue(val) {
|
||||
let type;
|
||||
switch (typeof val) {
|
||||
case 'string':
|
||||
|
|
@ -66,13 +65,28 @@ export function serializeExtension(extension) {
|
|||
case 'boolean':
|
||||
type = 'b';
|
||||
break;
|
||||
case 'object':
|
||||
if (Array.isArray(val)) {
|
||||
type = 'av';
|
||||
val = val.map(v => packValue(v));
|
||||
} else {
|
||||
type = 'a{sv}';
|
||||
let res = {};
|
||||
for (let key in val) {
|
||||
let packed = packValue(val[key]);
|
||||
if (packed)
|
||||
res[key] = packed;
|
||||
}
|
||||
val = res;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
return null;
|
||||
}
|
||||
res[key] = GLib.Variant.new(type, val);
|
||||
return GLib.Variant.new(type, val);
|
||||
}
|
||||
|
||||
return res;
|
||||
return packValue(obj).deepUnpack();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,7 +98,7 @@ export function serializeExtension(extension) {
|
|||
export function deserializeExtension(variant) {
|
||||
let res = {metadata: {}};
|
||||
for (let prop in variant) {
|
||||
let val = variant[prop].unpack();
|
||||
let val = variant[prop].recursiveUnpack();
|
||||
if (SERIALIZED_PROPERTIES.includes(prop))
|
||||
res[prop] = val;
|
||||
else
|
||||
|
|
@ -95,3 +109,64 @@ export function deserializeExtension(variant) {
|
|||
res.dir = Gio.File.new_for_path(res.path);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load extension metadata from directory
|
||||
*
|
||||
* @param {string} uuid of the extension
|
||||
* @param {GioFile} dir to load metadata from
|
||||
* @returns {object}
|
||||
*/
|
||||
export function loadExtensionMetadata(uuid, dir) {
|
||||
const dirName = dir.get_basename();
|
||||
if (dirName !== uuid)
|
||||
throw new Error(`Directory name "${dirName}" does not match UUID "${uuid}"`);
|
||||
|
||||
const metadataFile = dir.get_child('metadata.json');
|
||||
if (!metadataFile.query_exists(null))
|
||||
throw new Error('Missing metadata.json');
|
||||
|
||||
let metadataContents, success_;
|
||||
try {
|
||||
[success_, metadataContents] = metadataFile.load_contents(null);
|
||||
metadataContents = new TextDecoder().decode(metadataContents);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load metadata.json: ${e}`);
|
||||
}
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(metadataContents);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to parse metadata.json: ${e}`);
|
||||
}
|
||||
|
||||
const requiredProperties = [{
|
||||
prop: 'uuid',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'name',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'description',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'shell-version',
|
||||
typeName: 'string array',
|
||||
typeCheck: v => Array.isArray(v) && v.length > 0 && v.every(e => typeof e === 'string'),
|
||||
}];
|
||||
for (let i = 0; i < requiredProperties.length; i++) {
|
||||
const {
|
||||
prop, typeName, typeCheck = v => typeof v === typeName,
|
||||
} = requiredProperties[i];
|
||||
|
||||
if (!meta[prop])
|
||||
throw new Error(`missing "${prop}" property in metadata.json`);
|
||||
if (!typeCheck(meta[prop]))
|
||||
throw new Error(`property "${prop}" is not of type ${typeName}`);
|
||||
}
|
||||
|
||||
if (uuid !== meta.uuid)
|
||||
throw new Error(`UUID "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import * as Signals from '../misc/signals.js';
|
|||
import * as Config from '../misc/config.js';
|
||||
import * as ExtensionDownloader from './extensionDownloader.js';
|
||||
import {formatError} from '../misc/errorUtils.js';
|
||||
import {ExtensionState, ExtensionType} from '../misc/extensionUtils.js';
|
||||
import {
|
||||
ExtensionState, ExtensionType, loadExtensionMetadata
|
||||
} from '../misc/extensionUtils.js';
|
||||
import * as FileUtils from '../misc/fileUtils.js';
|
||||
import * as Main from './main.js';
|
||||
import * as MessageTray from './messageTray.js';
|
||||
|
|
@ -379,55 +381,10 @@ export class ExtensionManager extends Signals.EventEmitter {
|
|||
}
|
||||
|
||||
createExtensionObject(uuid, dir, type) {
|
||||
let metadataFile = dir.get_child('metadata.json');
|
||||
if (!metadataFile.query_exists(null))
|
||||
throw new Error('Missing metadata.json');
|
||||
|
||||
let metadataContents, success_;
|
||||
try {
|
||||
[success_, metadataContents] = metadataFile.load_contents(null);
|
||||
metadataContents = new TextDecoder().decode(metadataContents);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load metadata.json: ${e}`);
|
||||
}
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(metadataContents);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to parse metadata.json: ${e}`);
|
||||
}
|
||||
|
||||
const requiredProperties = [{
|
||||
prop: 'uuid',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'name',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'description',
|
||||
typeName: 'string',
|
||||
}, {
|
||||
prop: 'shell-version',
|
||||
typeName: 'string array',
|
||||
typeCheck: v => Array.isArray(v) && v.length > 0 && v.every(e => typeof e === 'string'),
|
||||
}];
|
||||
for (let i = 0; i < requiredProperties.length; i++) {
|
||||
const {
|
||||
prop, typeName, typeCheck = v => typeof v === typeName,
|
||||
} = requiredProperties[i];
|
||||
|
||||
if (!meta[prop])
|
||||
throw new Error(`missing "${prop}" property in metadata.json`);
|
||||
if (!typeCheck(meta[prop]))
|
||||
throw new Error(`property "${prop}" is not of type ${typeName}`);
|
||||
}
|
||||
|
||||
if (uuid !== meta.uuid)
|
||||
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
|
||||
|
||||
let extension = {
|
||||
metadata: meta,
|
||||
uuid: meta.uuid,
|
||||
const metadata = loadExtensionMetadata(uuid, dir);
|
||||
const extension = {
|
||||
metadata,
|
||||
uuid,
|
||||
type,
|
||||
dir,
|
||||
path: dir.get_path(),
|
||||
|
|
@ -436,7 +393,7 @@ export class ExtensionManager extends Signals.EventEmitter {
|
|||
enabled: this._enabledExtensions.includes(uuid),
|
||||
hasUpdate: false,
|
||||
canChange: false,
|
||||
sessionModes: meta['session-modes'] ? meta['session-modes'] : ['user'],
|
||||
sessionModes: metadata['session-modes'] ?? ['user'],
|
||||
};
|
||||
this._extensions.set(uuid, extension);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ unit_testenv.append('GI_TYPELIB_PATH', shell_typelib_path, separator: ':')
|
|||
unit_testenv.append('GI_TYPELIB_PATH', st_typelib_path, separator: ':')
|
||||
|
||||
unit_tests = [
|
||||
'extensionUtils',
|
||||
'highlighter',
|
||||
'injectionManager',
|
||||
'insertSorted',
|
||||
|
|
|
|||
141
tests/unit/extensionUtils.js
Normal file
141
tests/unit/extensionUtils.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
import 'resource:///org/gnome/shell/ui/environment.js';
|
||||
import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js';
|
||||
|
||||
const fixturesDir = Gio.File.new_for_uri(`${import.meta.url}/../fixtures/extensions`);
|
||||
|
||||
describe('loadExtensionMetadata()', () => {
|
||||
const {loadExtensionMetadata} = ExtensionUtils;
|
||||
|
||||
it('fails if directory name does not match requested UUID', () => {
|
||||
const dir = fixturesDir.get_child('valid');
|
||||
expect(() => loadExtensionMetadata('invalid', dir))
|
||||
.toThrowError(/does not match UUID/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json is missing', () => {
|
||||
const dir = fixturesDir.get_child('empty');
|
||||
expect(() => loadExtensionMetadata('empty', dir))
|
||||
.toThrowError(/Missing metadata/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json is not valid JSON', () => {
|
||||
const dir = fixturesDir.get_child('invalid');
|
||||
expect(() => loadExtensionMetadata('invalid', dir))
|
||||
.toThrowError(/parse metadata/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json misses "uuid" property', () => {
|
||||
const dir = fixturesDir.get_child('missing-uuid');
|
||||
expect(() => loadExtensionMetadata('missing-uuid', dir))
|
||||
.toThrowError(/missing "uuid"/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json misses "name" property', () => {
|
||||
const dir = fixturesDir.get_child('missing-name');
|
||||
expect(() => loadExtensionMetadata('missing-name', dir))
|
||||
.toThrowError(/missing "name"/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json misses "description" property', () => {
|
||||
const dir = fixturesDir.get_child('missing-description');
|
||||
expect(() => loadExtensionMetadata('missing-description', dir))
|
||||
.toThrowError(/missing "description"/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json misses "shell-version" property', () => {
|
||||
const dir = fixturesDir.get_child('missing-shell-version');
|
||||
expect(() => loadExtensionMetadata('missing-shell-version', dir))
|
||||
.toThrowError(/missing "shell-version"/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json "uuid" property is not a string', () => {
|
||||
const dir = fixturesDir.get_child('invalid-uuid');
|
||||
expect(() => loadExtensionMetadata('invalid-uuid', dir))
|
||||
.toThrowError(/"uuid" is not of type/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json "shell-version" property is not an array', () => {
|
||||
const dir = fixturesDir.get_child('invalid-shell-version1');
|
||||
expect(() => loadExtensionMetadata('invalid-shell-version1', dir))
|
||||
.toThrowError(/"shell-version" is not of type/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json "shell-version" property does not contain strings', () => {
|
||||
const dir = fixturesDir.get_child('invalid-shell-version2');
|
||||
expect(() => loadExtensionMetadata('invalid-shell-version2', dir))
|
||||
.toThrowError(/"shell-version" is not of type/);
|
||||
});
|
||||
|
||||
it('fails if metadata.json "uuid" property does not match directory name', () => {
|
||||
const dir = fixturesDir.get_child('wrong-uuid');
|
||||
expect(() => loadExtensionMetadata('wrong-uuid', dir))
|
||||
.toThrowError(/does not match directory/);
|
||||
});
|
||||
|
||||
it('loads valid metadata.json', () => {
|
||||
const dir = fixturesDir.get_child('valid');
|
||||
expect(() => loadExtensionMetadata('valid', dir)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeExtension()', () => {
|
||||
const {
|
||||
loadExtensionMetadata, serializeExtension, deserializeExtension, ExtensionType,
|
||||
} = ExtensionUtils;
|
||||
|
||||
// based on ExtensionManager
|
||||
function createExtensionObject(uuid, dir, type) {
|
||||
const metadata = loadExtensionMetadata(uuid, dir);
|
||||
const extension = {
|
||||
metadata,
|
||||
uuid,
|
||||
type,
|
||||
dir,
|
||||
path: dir.get_path(),
|
||||
error: '',
|
||||
hasPrefs: dir.get_child('prefs.js').query_exists(null),
|
||||
enabled: false,
|
||||
hasUpdate: false,
|
||||
canChange: false,
|
||||
sessionModes: metadata['session-modes'] ?? ['user'],
|
||||
};
|
||||
return extension;
|
||||
}
|
||||
const uuid = 'valid';
|
||||
const ext = createExtensionObject(uuid,
|
||||
fixturesDir.get_child(uuid), ExtensionType.PER_USER);
|
||||
let serialized;
|
||||
|
||||
beforeAll(() => {
|
||||
jasmine.addCustomEqualityTester((file1, file2) => {
|
||||
if (file1 instanceof Gio.File && file2 instanceof Gio.File)
|
||||
return file1.equal(file2);
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('produces output that can be used as variant of the expected type', () => {
|
||||
expect(() => {
|
||||
serialized = serializeExtension(ext);
|
||||
}).not.toThrow();
|
||||
|
||||
let v;
|
||||
expect(() => {
|
||||
v = new GLib.Variant('a{sv}', serialized);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(v.is_of_type(new GLib.VariantType('a{sv}'))).toBeTrue();
|
||||
});
|
||||
|
||||
it('produces output that can be deserialized', () => {
|
||||
let deserialized;
|
||||
expect(() => {
|
||||
deserialized = deserializeExtension(serialized);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(deserialized).toEqual(ext);
|
||||
});
|
||||
});
|
||||
0
tests/unit/fixtures/extensions/empty/.gitignore
vendored
Normal file
0
tests/unit/fixtures/extensions/empty/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"uuid": "invalid-shell-version1",
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": "45"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"uuid": "invalid-shell-version2",
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
45,
|
||||
46
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"uuid": {},
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
0
tests/unit/fixtures/extensions/invalid/metadata.json
Normal file
0
tests/unit/fixtures/extensions/invalid/metadata.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"uuid": "missing-description",
|
||||
"name": "Some Name",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"uuid": "missing-name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"uuid": "missing-shell-version",
|
||||
"name": "Some Name",
|
||||
"description": "Some Description"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
9
tests/unit/fixtures/extensions/valid/metadata.json
Normal file
9
tests/unit/fixtures/extensions/valid/metadata.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"uuid": "valid",
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
9
tests/unit/fixtures/extensions/wrong-uuid/metadata.json
Normal file
9
tests/unit/fixtures/extensions/wrong-uuid/metadata.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"uuid": "some-uuid",
|
||||
"name": "Some Name",
|
||||
"description": "Some Description",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue