feat: Enable capa problem editor for components in libraries (#1290)

* feat: enable the problem editor for library components

* fix: don't try to load "advanced settings" when editing problem in library

* fix: don't fetch images when editing problem in library

* docs: add a note about plans for the editor modal

* fix: choosing a problem type then cancelling resulted in an error

* chore: remove unused mockApi, clean up problematic 'module' self import

* test: update workflow test to test problem editor

* feat: show capa content summary on cards in library search results

* docs: fix comment typos found in code review

* refactor: add 'key-utils' to consolidate opaque key logic
This commit is contained in:
Braden MacDonald
2024-09-18 10:45:41 -07:00
committed by GitHub
parent b01090902a
commit 314dfa60e2
29 changed files with 466 additions and 617 deletions

View File

@@ -0,0 +1,78 @@
import {
getBlockType,
getLibraryId,
isLibraryKey,
isLibraryV1Key,
} from './key-utils';
describe('component utils', () => {
describe('getBlockType', () => {
for (const [input, expected] of [
['lb:org:lib:html:id', 'html'],
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'html'],
['lb:Axim:beta:problem:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'problem'],
]) {
it(`returns '${expected}' for usage key '${input}'`, () => {
expect(getBlockType(input)).toStrictEqual(expected);
});
}
for (const input of ['', undefined, null, 'not a key', 'lb:foo']) {
it(`throws an exception for usage key '${input}'`, () => {
expect(() => getBlockType(input as any)).toThrow(`Invalid usageKey: ${input}`);
});
}
});
describe('getLibraryId', () => {
for (const [input, expected] of [
['lb:org:lib:html:id', 'lib:org:lib'],
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'lib:OpenCraftX:ALPHA'],
['lb:Axim:beta:problem:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'lib:Axim:beta'],
]) {
it(`returns '${expected}' for usage key '${input}'`, () => {
expect(getLibraryId(input)).toStrictEqual(expected);
});
}
for (const input of ['', undefined, null, 'not a key', 'lb:foo']) {
it(`throws an exception for usage key '${input}'`, () => {
expect(() => getLibraryId(input as any)).toThrow(`Invalid usageKey: ${input}`);
});
}
});
describe('isLibraryKey', () => {
for (const [input, expected] of [
['lib:org:lib', true],
['lib:OpenCraftX:ALPHA', true],
['lb:org:lib:html:id', false],
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', false],
['library-v1:AximX+L1', false],
['course-v1:AximX+TS100+23', false],
['', false],
[undefined, false],
] as const) {
it(`returns '${expected}' for learning context key '${input}'`, () => {
expect(isLibraryKey(input)).toStrictEqual(expected);
});
}
});
describe('isLibraryV1Key', () => {
for (const [input, expected] of [
['library-v1:AximX+L1', true],
['lib:org:lib', false],
['lib:OpenCraftX:ALPHA', false],
['lb:org:lib:html:id', false],
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', false],
['course-v1:AximX+TS100+23', false],
['', false],
[undefined, false],
] as const) {
it(`returns '${expected}' for learning context key '${input}'`, () => {
expect(isLibraryV1Key(input)).toStrictEqual(expected);
});
}
});
});

40
src/generic/key-utils.ts Normal file
View File

@@ -0,0 +1,40 @@
/**
* Given a usage key like `lb:org:lib:html:id`, get the type (e.g. `html`)
* @param usageKey e.g. `lb:org:lib:html:id`
* @returns The block type as a string
*/
export function getBlockType(usageKey: string): string {
if (usageKey && usageKey.startsWith('lb:')) {
const blockType = usageKey.split(':')[3];
if (blockType) {
return blockType;
}
}
throw new Error(`Invalid usageKey: ${usageKey}`);
}
/**
* Given a usage key like `lb:org:lib:html:id`, get the library key
* @param usageKey e.g. `lb:org:lib:html:id`
* @returns The library key, e.g. `lib:org:lib`
*/
export function getLibraryId(usageKey: string): string {
if (usageKey && usageKey.startsWith('lb:')) {
const org = usageKey.split(':')[1];
const lib = usageKey.split(':')[2];
if (org && lib) {
return `lib:${org}:${lib}`;
}
}
throw new Error(`Invalid usageKey: ${usageKey}`);
}
/** Check if this is a V2 library key. */
export function isLibraryKey(learningContextKey: string | undefined): learningContextKey is string {
return typeof learningContextKey === 'string' && learningContextKey.startsWith('lib:');
}
/** Check if this is a V1 library key. */
export function isLibraryV1Key(learningContextKey: string | undefined): learningContextKey is string {
return typeof learningContextKey === 'string' && learningContextKey.startsWith('library-v1:');
}