diff --git a/lib/specifications/ComponentProject.js b/lib/specifications/ComponentProject.js
index ec9de3971..2fad4065d 100644
--- a/lib/specifications/ComponentProject.js
+++ b/lib/specifications/ComponentProject.js
@@ -101,32 +101,27 @@ class ComponentProject extends Project {
* @returns {module:@ui5/fs.ReaderCollection} A reader collection instance
*/
getReader({style = "buildtime"} = {}) {
- // TODO: Additional parameter 'includeWorkspace' to include reader to relevant Memory Adapter?
// TODO: Additional style 'ABAP' using "sap.platform.abap".uri from manifest.json?
+
+ if (style === "runtime" && this._isRuntimeNamespaced) {
+ // If the project's runtime requires namespaces, paths are identical to "buildtime" style
+ style = "buildtime";
+ }
let reader;
- let testReader;
switch (style) {
case "buildtime":
- reader = this._getFlatSourceReader(`/resources/${this._namespace}/`);
- testReader = this._getFlatTestReader(`/test-resources/${this._namespace}/`);
- if (testReader) {
- reader = resourceFactory.createReaderCollection({
- name: `Reader collection for project ${this.getName()}`,
- readers: [reader, testReader]
- });
- }
+ reader = this._getReader();
break;
case "runtime":
- if (this._isRuntimeNamespaced) {
- // Same as buildtime
- return this.getReader();
- }
- // TODO 3.0: Refactor this
- return this.getReader().link({
+ // No test-resources for runtime resource access,
+ // unless runtime is namespaced
+ reader = this.getReader().link({
linkPath: `/`,
targetPath: `/resources/${this._namespace}/`
});
+ break;
case "flat":
+ // No test-resources for flat resource access
reader = this._getFlatSourceReader("/");
break;
default:
@@ -138,16 +133,7 @@ class ComponentProject extends Project {
}
/**
- * Get a resource reader for the sources of the project (not including any test resources)
- *
- * @returns {module:@ui5/fs.ReaderCollection} Reader collection
- */
- _getFullSourceReader() {
- throw new Error(`_getFullSourceReader must be implemented by subclass ${this.constructor.name}`);
- }
-
- /**
- * TODO
+ * Get a resource reader for the resources of the project
*
* @returns {module:@ui5/fs.ReaderCollection} Reader collection
*/
@@ -156,15 +142,7 @@ class ComponentProject extends Project {
}
/**
- * Get a resource reader for the test-sources of the project
- *
- * @returns {module:@ui5/fs.ReaderCollection} Reader collection
- */
- _getFullTestReader() {
- throw new Error(`_getFullTestReader must be implemented by subclass ${this.constructor.name}`);
- }
- /**
- * TODO
+ * Get a resource reader for the test resources of the project
*
* @returns {module:@ui5/fs.ReaderCollection} Reader collection
*/
@@ -180,63 +158,98 @@ class ComponentProject extends Project {
*/
getWorkspace() {
// Workspace is always of style "buildtime"
- const reader = this.getReader({
- style: "buildtime"
- });
-
- const writer = this._getWriter();
return resourceFactory.createWorkspace({
- reader,
- writer
+ name: `Workspace for project ${this.getName()}`,
+ reader: this._getReader(),
+ writer: this._getWriter().collection
});
}
_getWriter() {
- if (!this._writer) {
+ if (!this._writers) {
// writer is always of style "buildtime"
- this._writer = resourceFactory.createAdapter({
+ const namespaceWriter = resourceFactory.createAdapter({
+ virBasePath: "/",
+ project: this
+ });
+
+ const generalWriter = resourceFactory.createAdapter({
virBasePath: "/",
project: this
});
+
+ const collection = resourceFactory.createWriterCollection({
+ name: `Writers for project ${this.getName()}`,
+ writerMapping: {
+ [`/resources/${this._namespace}/`]: namespaceWriter,
+ [`/test-resources/${this._namespace}/`]: namespaceWriter,
+ [`/`]: generalWriter
+ }
+ });
+
+ this._writers = {
+ namespaceWriter,
+ generalWriter,
+ collection
+ };
+ }
+ return this._writers;
+ }
+
+ _getReader() {
+ let reader = this._getFlatSourceReader(`/resources/${this._namespace}/`);
+ const testReader = this._getFlatTestReader(`/test-resources/${this._namespace}/`);
+ if (testReader) {
+ reader = resourceFactory.createReaderCollection({
+ name: `Reader collection for project ${this.getName()}`,
+ readers: [reader, testReader]
+ });
}
- return this._writer;
+ return reader;
}
- _addWriter(reader, style = "buildtime") {
- let writer = this._getWriter();
+ _addWriter(reader, style) {
+ const {namespaceWriter, generalWriter} = this._getWriter();
+
+ if (style === "runtime" && this._isRuntimeNamespaced) {
+ // If the project's runtime requires namespaces, "runtime" paths are identical to "buildtime" paths
+ style = "buildtime";
+ }
+ const readers = [];
switch (style) {
case "buildtime": {
// Writer already uses buildtime style
+ readers.push(namespaceWriter);
+ readers.push(generalWriter);
break;
}
case "runtime": {
- if (this._isRuntimeNamespaced) {
- // Same as buildtime
- return this._addWriter(reader);
- }
-
- // Rewrite paths from "runtime" to "buildtime"
- writer = writer.link({
+ // Runtime is not namespaced: link namespace to /
+ readers.push(namespaceWriter.link({
linkPath: `/`,
targetPath: `/resources/${this._namespace}/`
- });
+ }));
+ // Add general writer as is
+ readers.push(generalWriter);
break;
}
case "flat": {
// Rewrite paths from "flat" to "buildtime"
- writer = writer.link({
+ readers.push(namespaceWriter.link({
linkPath: `/`,
targetPath: `/resources/${this._namespace}/`
- });
+ }));
+ // General writer resources can't be flattened, so they are not available
break;
}
default:
throw new Error(`Unknown path mapping style ${style}`);
}
+ readers.push(reader);
return resourceFactory.createReaderCollectionPrioritized({
name: `Reader/Writer collection for project ${this.getName()}`,
- readers: [writer, reader]
+ readers
});
}
diff --git a/lib/specifications/types/Application.js b/lib/specifications/types/Application.js
index 38123c0e8..d11509704 100644
--- a/lib/specifications/types/Application.js
+++ b/lib/specifications/types/Application.js
@@ -46,7 +46,7 @@ class Application extends ComponentProject {
return null; // Applications do not have a dedicated test directory
}
- _getSourceReader() {
+ _getRawSourceReader() {
return resourceFactory.createReader({
fsBasePath: fsPath.join(this.getPath(), this._webappPath),
virBasePath: "/",
@@ -190,7 +190,7 @@ class Application extends ComponentProject {
if (this._pManifests[filePath]) {
return this._pManifests[filePath];
}
- return this._pManifests[filePath] = this._getSourceReader().byPath(filePath)
+ return this._pManifests[filePath] = this._getRawSourceReader().byPath(filePath)
.then(async (resource) => {
if (!resource) {
throw new Error(
diff --git a/lib/specifications/types/Library.js b/lib/specifications/types/Library.js
index dba64ad75..53c72d013 100644
--- a/lib/specifications/types/Library.js
+++ b/lib/specifications/types/Library.js
@@ -15,6 +15,8 @@ class Library extends ComponentProject {
this._srcPath = "src";
this._testPath = "test";
this._testPathExists = false;
+ this._isSourceNamespaced = true;
+
this._propertiesFilesSourceEncoding = "UTF-8";
}
@@ -29,27 +31,14 @@ class Library extends ComponentProject {
}
/* === Resource Access === */
-
- /**
- *
- * Get a resource reader for the sources of the project (excluding any test resources)
- * In the future the path structure can be flat or namespaced depending on the project
- *
- * @returns {module:@ui5/fs.ReaderCollection} Reader collection
- */
- _getSourceReader() {
- return resourceFactory.createReader({
- fsBasePath: fsPath.join(this.getPath(), this._srcPath),
- virBasePath: "/",
- name: `Source reader for library project ${this.getName()}`,
- project: this
- });
- }
-
_getFlatSourceReader(virBasePath = "/") {
// TODO: Throw for libraries with additional namespaces like sap.ui.core?
+ let fsBasePath = fsPath.join(this.getPath(), this._srcPath);
+ if (this._isSourceNamespaced) {
+ fsBasePath = fsPath.join(fsBasePath, ...this._namespace.split("/"));
+ }
return resourceFactory.createReader({
- fsBasePath: fsPath.join(this.getPath(), this._srcPath, ...this._namespace.split("/")),
+ fsBasePath,
virBasePath,
name: `Source reader for library project ${this.getName()}`,
project: this
@@ -60,8 +49,12 @@ class Library extends ComponentProject {
if (!this._testPathExists) {
return null;
}
+ let fsBasePath = fsPath.join(this.getPath(), this._testPath);
+ if (this._isSourceNamespaced) {
+ fsBasePath = fsPath.join(fsBasePath, ...this._namespace.split("/"));
+ }
const testReader = resourceFactory.createReader({
- fsBasePath: fsPath.join(this.getPath(), this._testPath, ...this._namespace.split("/")),
+ fsBasePath,
virBasePath,
name: `Runtime test-resources reader for library project ${this.getName()}`,
project: this
@@ -69,6 +62,22 @@ class Library extends ComponentProject {
return testReader;
}
+ /**
+ *
+ * Get a resource reader for the sources of the project (excluding any test resources)
+ * In the future the path structure can be flat or namespaced depending on the project
+ *
+ * @returns {module:@ui5/fs.ReaderCollection} Reader collection
+ */
+ _getRawSourceReader() {
+ return resourceFactory.createReader({
+ fsBasePath: fsPath.join(this.getPath(), this._srcPath),
+ virBasePath: "/",
+ name: `Source reader for library project ${this.getName()}`,
+ project: this
+ });
+ }
+
/* === Internals === */
/**
* @private
@@ -246,11 +255,16 @@ class Library extends ComponentProject {
}
namespace = libraryNs.replace(/\./g, "/");
- namespacePath = namespacePath.replace("/", ""); // remove leading slash
- if (namespacePath !== namespace) {
- throw new Error(
- `Detected namespace "${namespace}" does not match detected directory ` +
- `structure "${namespacePath}" for project ${this.getName()}`);
+ if (namespacePath === "/") {
+ this._log.verbose(`Detected flat library source structure for project ${this.getName()}`);
+ this._isSourceNamespaced = false;
+ } else {
+ namespacePath = namespacePath.replace("/", ""); // remove leading slash
+ if (namespacePath !== namespace) {
+ throw new Error(
+ `Detected namespace "${namespace}" does not match detected directory ` +
+ `structure "${namespacePath}" for project ${this.getName()}`);
+ }
}
} else {
try {
@@ -384,7 +398,7 @@ class Library extends ComponentProject {
if (this._pManifest) {
return this._pManifest;
}
- return this._pManifest = this._getSourceReader().byGlob("**/manifest.json")
+ return this._pManifest = this._getRawSourceReader().byGlob("**/manifest.json")
.then(async (manifestResources) => {
if (!manifestResources.length) {
throw new Error(`Could not find manifest.json file for project ${this.getName()}`);
@@ -416,7 +430,7 @@ class Library extends ComponentProject {
if (this._pDotLibrary) {
return this._pDotLibrary;
}
- return this._pDotLibrary = this._getSourceReader().byGlob("**/.library")
+ return this._pDotLibrary = this._getRawSourceReader().byGlob("**/.library")
.then(async (dotLibraryResources) => {
if (!dotLibraryResources.length) {
throw new Error(`Could not find .library file for project ${this.getName()}`);
@@ -456,7 +470,7 @@ class Library extends ComponentProject {
if (this._pLibraryJs) {
return this._pLibraryJs;
}
- return this._pLibraryJs = this._getSourceReader().byGlob("**/library.js")
+ return this._pLibraryJs = this._getRawSourceReader().byGlob("**/library.js")
.then(async (libraryJsResources) => {
if (!libraryJsResources.length) {
throw new Error(`Could not find library.js file for project ${this.getName()}`);
diff --git a/lib/specifications/types/ThemeLibrary.js b/lib/specifications/types/ThemeLibrary.js
index b0f2b635d..54154ec1d 100644
--- a/lib/specifications/types/ThemeLibrary.js
+++ b/lib/specifications/types/ThemeLibrary.js
@@ -8,6 +8,7 @@ class ThemeLibrary extends Project {
this._srcPath = "src";
this._testPath = "test";
+ this._testPathExists = false;
this._writer = null;
}
diff --git a/test/fixtures/library.h/src/.library b/test/fixtures/library.h/src/.library
new file mode 100644
index 000000000..8de6bd2eb
--- /dev/null
+++ b/test/fixtures/library.h/src/.library
@@ -0,0 +1,11 @@
+
+
+
+ library.h
+ SAP SE
+ ${copyright}
+ ${version}
+
+ Library G
+
+
diff --git a/test/fixtures/library.h/src/manifest.json b/test/fixtures/library.h/src/manifest.json
new file mode 100644
index 000000000..2279cb6ce
--- /dev/null
+++ b/test/fixtures/library.h/src/manifest.json
@@ -0,0 +1,26 @@
+{
+ "_version": "1.21.0",
+ "sap.app": {
+ "id": "library.h",
+ "type": "library",
+ "embeds": [],
+ "applicationVersion": {
+ "version": "1.0.0"
+ },
+ "title": "Library H",
+ "description": "Library H"
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "supportedThemes": []
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "minUI5Version": "1.0",
+ "libs": {}
+ },
+ "library": {
+ "i18n": false
+ }
+ }
+}
diff --git a/test/fixtures/library.h/src/some.js b/test/fixtures/library.h/src/some.js
new file mode 100644
index 000000000..81e734360
--- /dev/null
+++ b/test/fixtures/library.h/src/some.js
@@ -0,0 +1,4 @@
+/*!
+ * ${copyright}
+ */
+console.log('HelloWorld');
\ No newline at end of file
diff --git a/test/fixtures/library.h/ui5.yaml b/test/fixtures/library.h/ui5.yaml
new file mode 100644
index 000000000..cbea83db5
--- /dev/null
+++ b/test/fixtures/library.h/ui5.yaml
@@ -0,0 +1,5 @@
+---
+specVersion: "2.6"
+type: library
+metadata:
+ name: library.h
diff --git a/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme
new file mode 100644
index 000000000..4c62f2611
--- /dev/null
+++ b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme
@@ -0,0 +1,9 @@
+
+
+
+ my_theme
+ me
+ ${copyright}
+ ${version}
+
+
\ No newline at end of file
diff --git a/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming
new file mode 100644
index 000000000..83b6c785a
--- /dev/null
+++ b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming
@@ -0,0 +1,27 @@
+{
+ "sEntity": "Theme",
+ "sId": "sap_belize",
+ "oExtends": "base",
+ "sVendor": "SAP",
+ "aBundled": ["sap_belize_plus"],
+ "mCssScopes": {
+ "library": {
+ "sBaseFile": "library",
+ "sEmbeddingMethod": "APPEND",
+ "aScopes": [
+ {
+ "sLabel": "Contrast",
+ "sSelector": "sapContrast",
+ "sEmbeddedFile": "sap_belize_plus.library",
+ "sEmbeddedCompareFile": "library",
+ "sThemeIdSuffix": "Contrast",
+ "sThemability": "PUBLIC",
+ "aThemabilityFilter": [
+ "Color"
+ ],
+ "rExcludeSelector": "\\.sapContrastPlus\\W"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less
new file mode 100644
index 000000000..d3286002b
--- /dev/null
+++ b/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less
@@ -0,0 +1,9 @@
+/*!
+ * ${copyright}
+ */
+
+@mycolor: blue;
+
+.sapUiBody {
+ background-color: @mycolor;
+}
diff --git a/test/fixtures/theme.library.e/test/theme/library/e/Test.html b/test/fixtures/theme.library.e/test/theme/library/e/Test.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/fixtures/theme.library.e/ui5.yaml b/test/fixtures/theme.library.e/ui5.yaml
new file mode 100644
index 000000000..cf89c2432
--- /dev/null
+++ b/test/fixtures/theme.library.e/ui5.yaml
@@ -0,0 +1,9 @@
+---
+specVersion: "1.1"
+type: theme-library
+metadata:
+ name: theme.library.e
+ copyright: |-
+ UI development toolkit for HTML5 (OpenUI5)
+ * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
diff --git a/test/lib/specifications/types/Application.js b/test/lib/specifications/types/Application.js
index 05b4d9c68..769e9d495 100644
--- a/test/lib/specifications/types/Application.js
+++ b/test/lib/specifications/types/Application.js
@@ -1,6 +1,7 @@
const test = require("ava");
const path = require("path");
const sinon = require("sinon");
+const {createResource} = require("@ui5/fs").resourceFactory;
const Specification = require("../../../../lib/specifications/Specification");
const Application = require("../../../../lib/specifications/types/Application");
@@ -105,6 +106,45 @@ test("Modify project resources via workspace and access via flat and runtime rea
t.is(await runtimeGlobResult[0].getString(), newContent, "Found resource (byGlob) has expected (changed) content");
});
+
+test("Read and write resources outside of app namespace", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ const workspace = await project.getWorkspace();
+
+ await workspace.write(createResource({
+ path: "/resources/my-custom-bundle.js"
+ }));
+
+ const buildtimeReader = await project.getReader({style: "buildtime"});
+ const buildtimeReaderResource = await buildtimeReader.byPath("/resources/my-custom-bundle.js");
+ t.truthy(buildtimeReaderResource, "Found the requested resource byPath (buildtime)");
+ t.is(buildtimeReaderResource.getPath(), "/resources/my-custom-bundle.js",
+ "Resource (byPath) has correct path (buildtime)");
+
+ const buildtimeGlobResult = await buildtimeReader.byGlob("**/my-custom-bundle.js");
+ t.is(buildtimeGlobResult.length, 1, "Found the requested resource byGlob (buildtime)");
+ t.is(buildtimeGlobResult[0].getPath(), "/resources/my-custom-bundle.js",
+ "Resource (byGlob) has correct path (buildtime)");
+
+ const flatReader = await project.getReader({style: "flat"});
+ const flatReaderResource = await flatReader.byPath("/resources/my-custom-bundle.js");
+ t.falsy(flatReaderResource, "Resource outside of app namespace can't be read using flat reader");
+
+ const flatGlobResult = await flatReader.byGlob("**/my-custom-bundle.js");
+ t.is(flatGlobResult.length, 0, "Resource outside of app namespace can't be found using flat reader");
+
+ const runtimeReader = await project.getReader({style: "runtime"});
+ const runtimeReaderResource = await runtimeReader.byPath("/resources/my-custom-bundle.js");
+ t.truthy(runtimeReaderResource, "Found the requested resource byPath (runtime)");
+ t.is(runtimeReaderResource.getPath(), "/resources/my-custom-bundle.js",
+ "Resource (byPath) has correct path (runtime)");
+
+ const runtimeGlobResult = await runtimeReader.byGlob("**/my-custom-bundle.js");
+ t.is(runtimeGlobResult.length, 1, "Found the requested resource byGlob (runtime)");
+ t.is(runtimeGlobResult[0].getPath(), "/resources/my-custom-bundle.js",
+ "Resource (byGlob) has correct path (runtime)");
+});
+
test("_configureAndValidatePaths: Default paths", async (t) => {
const project = await Specification.create(basicProjectInput);
@@ -267,7 +307,7 @@ test("_getManifest: invalid JSON", async (t) => {
getString: async () => "no json"
});
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byPath: byPathStub
};
@@ -299,7 +339,7 @@ test.serial("_getManifest: result is cached", async (t) => {
getString: async () => `{"pony": "no unicorn"}`
});
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byPath: byPathStub
};
@@ -324,7 +364,7 @@ test.serial("_getManifest: Caches successes and failures", async (t) => {
getString: getStringStub
});
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byPath: byPathStub
};
diff --git a/test/lib/specifications/types/Library.js b/test/lib/specifications/types/Library.js
index 259cc9d3b..2fb78dfa5 100644
--- a/test/lib/specifications/types/Library.js
+++ b/test/lib/specifications/types/Library.js
@@ -31,6 +31,21 @@ const basicProjectInput = {
}
};
+const libraryHPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.h");
+const flatProjectInput = {
+ id: "library.d.id",
+ version: "1.0.0",
+ modulePath: libraryHPath,
+ configuration: {
+ specVersion: "2.6",
+ kind: "project",
+ type: "library",
+ metadata: {
+ name: "library.h",
+ }
+ }
+};
+
test.afterEach.always((t) => {
sinon.restore();
mock.stopAll();
@@ -133,6 +148,14 @@ test("Modify project resources via workspace and access via flat and runtime rea
"Found resource (byGlob) has expected (changed) content (runtime)");
});
+test("Access flat project resources via reader: buildtime style", async (t) => {
+ const project = await Specification.create(flatProjectInput);
+ const reader = await project.getReader({style: "buildtime"});
+ const resource = await reader.byPath("/resources/library/h/some.js");
+ t.truthy(resource, "Found the requested resource");
+ t.is(resource.getPath(), "/resources/library/h/some.js", "Resource has correct path");
+});
+
test("_configureAndValidatePaths: Default paths", async (t) => {
const libraryEPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.e");
const projectInput = {
@@ -166,7 +189,6 @@ test("_configureAndValidatePaths: Test directory does not exist", async (t) => {
t.false(project._testPathExists, "Test path detected as non-existent");
});
-
test("_configureAndValidatePaths: Source directory does not exist", async (t) => {
const projectInput = clone(basicProjectInput);
projectInput.configuration.resources.configuration.paths.src = "does/not/exist";
@@ -340,7 +362,7 @@ test("_getManifest: Reads correctly", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -357,7 +379,7 @@ test("_getManifest: No manifest.json", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().resolves([]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -376,7 +398,7 @@ test("_getManifest: Invalid JSON", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -395,7 +417,7 @@ test("_getManifest: Propagates exception", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().rejects(new Error("because shark"));
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -417,7 +439,7 @@ test("_getManifest: Multiple manifest.json files", async (t) => {
getPath: () => "some other path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -435,7 +457,7 @@ test("_getManifest: Result is cached", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -461,7 +483,7 @@ test("_getDotLibrary: Reads correctly", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -478,7 +500,7 @@ test("_getDotLibrary: No .library file", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().resolves([]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -497,7 +519,7 @@ test("_getDotLibrary: Invalid XML", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -516,7 +538,7 @@ test("_getDotLibrary: Propagates exception", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().rejects(new Error("because shark"));
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -538,7 +560,7 @@ test("_getDotLibrary: Multiple .library files", async (t) => {
getPath: () => "some other path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -556,7 +578,7 @@ test("_getDotLibrary: Result is cached", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -581,7 +603,7 @@ test("_getLibraryJsPath: Reads correctly", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -597,7 +619,7 @@ test("_getLibraryJsPath: No library.js file", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().resolves([]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -613,7 +635,7 @@ test("_getLibraryJsPath: Propagates exception", async (t) => {
const project = await Specification.create(basicProjectInput);
const byGlobStub = sinon.stub().rejects(new Error("because shark"));
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -633,7 +655,7 @@ test("_getLibraryJsPath: Multiple library.js files", async (t) => {
getPath: () => "some other path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -650,7 +672,7 @@ test("_getLibraryJsPath: Result is cached", async (t) => {
getPath: () => "some path"
}]);
- project._getSourceReader = () => {
+ project._getRawSourceReader = () => {
return {
byGlob: byGlobStub
};
@@ -714,12 +736,48 @@ test("_getNamespace: from manifest.json with .library on same level", async (t)
});
sinon.stub(project, "_getDotLibrary").resolves({
content: {
- library: {name: "dot-pony"}
+ library: {name: {_: "dot-pony"}}
},
filePath: "/mani-pony/.library"
});
const res = await project._getNamespace();
- t.deepEqual(res, "mani-pony", "Returned correct namespace");
+ t.is(res, "mani-pony", "Returned correct namespace");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
+});
+
+test("_getNamespace: from manifest.json for flat project", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ sinon.stub(project, "_getManifest").resolves({
+ content: {
+ "sap.app": {
+ id: "mani-pony"
+ }
+ },
+ filePath: "/manifest.json"
+ });
+ sinon.stub(project, "_getDotLibrary").resolves({
+ content: {
+ library: {name: {_: "dot-pony"}}
+ },
+ filePath: "/.library"
+ });
+ const res = await project._getNamespace();
+ t.is(res, "mani-pony", "Returned correct namespace");
+ t.false(project._isSourceNamespaced, "Project flagged as flat source structure");
+});
+
+test("_getNamespace: from .library for flat project", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ sinon.stub(project, "_getManifest").rejects("No manifest aint' here");
+ sinon.stub(project, "_getDotLibrary").resolves({
+ content: {
+ library: {name: {_: "dot-pony"}}
+ },
+ filePath: "/.library"
+ });
+ const res = await project._getNamespace();
+ t.is(res, "dot-pony", "Returned correct namespace");
+ t.false(project._isSourceNamespaced, "Project flagged as flat source structure");
});
test("_getNamespace: from manifest.json with .library on same level but different directory", async (t) => {
@@ -762,7 +820,7 @@ test("_getNamespace: from manifest.json with not matching file path", async (t)
});
sinon.stub(project, "_getDotLibrary").resolves({
content: {
- library: {name: "dot-pony"}
+ library: {name: {_: "dot-pony"}}
},
filePath: "/different/namespace/.library"
});
@@ -806,6 +864,7 @@ test.serial("_getNamespace: from manifest.json without sap.app id", async (t) =>
`Namespace resolution from manifest.json failed for project library.d: ` +
`No sap.app/id configuration found in manifest.json of project library.d at ${manifestPath}`,
"correct verbose message");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test("_getNamespace: from .library", async (t) => {
@@ -819,6 +878,7 @@ test("_getNamespace: from .library", async (t) => {
});
const res = await project._getNamespace();
t.deepEqual(res, "dot-pony", "Returned correct namespace");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test("_getNamespace: from .library with ignored manifest.json on lower level", async (t) => {
@@ -839,6 +899,7 @@ test("_getNamespace: from .library with ignored manifest.json on lower level", a
});
const res = await project._getNamespace();
t.deepEqual(res, "dot-pony", "Returned correct namespace");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test("_getNamespace: manifest.json on higher level than .library", async (t) => {
@@ -889,6 +950,7 @@ test("_getNamespace: from .library with maven placeholder", async (t) => {
t.deepEqual(resolveMavenPlaceholderStub.getCall(0).args[0], "${mvn-pony}",
"resolveMavenPlaceholder called with correct argument");
t.deepEqual(res, "mvn-unicorn", "Returned correct namespace");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test("_getNamespace: from .library with not matching file path", async (t) => {
@@ -905,6 +967,7 @@ test("_getNamespace: from .library with not matching file path", async (t) => {
t.deepEqual(err.message, `Detected namespace "mvn-pony" does not match detected directory structure ` +
`"different/namespace" for project library.d`,
"Rejected with correct error message");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test("_getNamespace: from library.js", async (t) => {
@@ -914,6 +977,7 @@ test("_getNamespace: from library.js", async (t) => {
sinon.stub(project, "_getLibraryJsPath").resolves("/my/namespace/library.js");
const res = await project._getNamespace();
t.deepEqual(res, "my/namespace", "Returned correct namespace");
+ t.true(project._isSourceNamespaced, "Project still flagged as namespaced source structure");
});
test.serial("_getNamespace: from project root level library.js", async (t) => {
diff --git a/test/lib/specifications/types/ThemeLibrary.js b/test/lib/specifications/types/ThemeLibrary.js
new file mode 100644
index 000000000..e2f053f02
--- /dev/null
+++ b/test/lib/specifications/types/ThemeLibrary.js
@@ -0,0 +1,122 @@
+const test = require("ava");
+const path = require("path");
+const sinon = require("sinon");
+const mock = require("mock-require");
+const Specification = require("../../../../lib/specifications/Specification");
+
+function clone(obj) {
+ return JSON.parse(JSON.stringify(obj));
+}
+
+const themeLibraryEPath = path.join(__dirname, "..", "..", "..", "fixtures", "theme.library.e");
+const basicProjectInput = {
+ id: "theme.library.e.id",
+ version: "1.0.0",
+ modulePath: themeLibraryEPath,
+ configuration: {
+ specVersion: "2.6",
+ kind: "project",
+ type: "theme-library",
+ metadata: {
+ name: "theme.library.e",
+ copyright: "Some fancy copyright"
+ }
+ }
+};
+
+test.afterEach.always((t) => {
+ sinon.restore();
+ mock.stopAll();
+});
+
+test("Correct class", async (t) => {
+ const ThemeLibrary = mock.reRequire("../../../../lib/specifications/types/ThemeLibrary");
+ const project = await Specification.create(basicProjectInput);
+ t.true(project instanceof ThemeLibrary, `Is an instance of the ThemeLibrary class`);
+});
+
+test("getCopyright", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+
+ t.deepEqual(project.getCopyright(), "Some fancy copyright", "Copyright was read correctly");
+});
+
+test("Access project resources via reader", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ const reader = await project.getReader();
+ const resource = await reader.byPath("/resources/theme/library/e/themes/my_theme/.theme");
+ t.truthy(resource, "Found the requested resource");
+ t.is(resource.getPath(), "/resources/theme/library/e/themes/my_theme/.theme", "Resource has correct path");
+});
+
+test("Access project test-resources via reader", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ const reader = await project.getReader();
+ const resource = await reader.byPath("/test-resources/theme/library/e/Test.html");
+ t.truthy(resource, "Found the requested resource");
+ t.is(resource.getPath(), "/test-resources/theme/library/e/Test.html", "Resource has correct path");
+});
+
+test("Modify project resources via workspace and access via flat and runtime reader", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+ const workspace = await project.getWorkspace();
+ const workspaceResource = await workspace.byPath("/resources/theme/library/e/themes/my_theme/library.source.less");
+ t.truthy(workspaceResource, "Found resource in workspace");
+
+ const newContent = (await workspaceResource.getString()).replace("fancy", "fancy dancy");
+ workspaceResource.setString(newContent);
+ await workspace.write(workspaceResource);
+
+ const reader = await project.getReader();
+ const readerResource = await reader.byPath("/resources/theme/library/e/themes/my_theme/library.source.less");
+ t.truthy(readerResource, "Found the requested resource byPath");
+ t.is(readerResource.getPath(), "/resources/theme/library/e/themes/my_theme/library.source.less",
+ "Resource (byPath) has correct path");
+ t.is(await readerResource.getString(), newContent,
+ "Found resource (byPath) has expected (changed) content");
+
+ const globResult = await reader.byGlob("**/library.source.less");
+ t.is(globResult.length, 1, "Found the requested resource byGlob");
+ t.is(globResult[0].getPath(), "/resources/theme/library/e/themes/my_theme/library.source.less",
+ "Resource (byGlob) has correct path");
+ t.is(await globResult[0].getString(), newContent,
+ "Found resource (byGlob) has expected (changed) content");
+});
+
+test("_configureAndValidatePaths: Default paths", async (t) => {
+ const project = await Specification.create(basicProjectInput);
+
+ t.is(project._srcPath, "src", "Correct default path for src");
+ t.is(project._testPath, "test", "Correct default path for test");
+ t.true(project._testPathExists, "Test path detected as existing");
+});
+
+test("_configureAndValidatePaths: Test directory does not exist", async (t) => {
+ const projectInput = clone(basicProjectInput);
+ projectInput.configuration.resources = {
+ configuration: {
+ paths: {
+ test: "does/not/exist"
+ }
+ }
+ };
+ const project = await Specification.create(projectInput);
+
+ t.is(project._srcPath, "src", "Correct path for src");
+ t.is(project._testPath, "does/not/exist", "Correct path for test");
+ t.false(project._testPathExists, "Test path detected as non-existent");
+});
+
+test("_configureAndValidatePaths: Source directory does not exist", async (t) => {
+ const projectInput = clone(basicProjectInput);
+ projectInput.configuration.resources = {
+ configuration: {
+ paths: {
+ src: "does/not/exist"
+ }
+ }
+ };
+ const err = await t.throwsAsync(Specification.create(projectInput));
+
+ t.is(err.message, "Unable to find directory 'does/not/exist' in theme-library project theme.library.e");
+});