From c71a429c4e5d1e35936317d9fad5105da64887cd Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 9 Oct 2017 07:36:29 -0500 Subject: [PATCH 01/15] add rimraf script --- package.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 02bddc485e..bcbae98ec1 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,15 @@ }, "scripts": { "global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup", - "clean:coverage": "rimraf coverage", - "clean:dist": "rimraf dist", - "clean:doc": "rimraf doc", - "clean:log": "rimraf *.log*", - "clean:json": "rimraf *.records.json", - "clean:node": "rimraf node_modules", + "clean:coverage": "yarn run rimraf coverage", + "clean:dist": "yarn run rimraf dist", + "clean:doc": "yarn run rimraf doc", + "clean:log": "yarn run rimraf *.log*", + "clean:json": "yarn run rimraf *.records.json", + "clean:node": "yarn run rimraf node_modules", "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json", "clean": "yarn run clean:prod && yarn run clean:node", + "rimraf": "rimraf", "prebuild": "yarn run clean:dist", "prebuild:aot": "yarn run prebuild", "prebuild:prod": "yarn run prebuild", From d4602a23f7b8a85acc5a71e2509c623126f9e6ad Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 9 Oct 2017 07:38:26 -0500 Subject: [PATCH 02/15] upgrade some dependencies --- package.json | 14 ++-- yarn.lock | 214 ++++++++++++++++++++++++--------------------------- 2 files changed, 106 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index bcbae98ec1..6f761ef47c 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "methods": "1.1.2", "morgan": "1.9.0", "ngx-pagination": "3.0.1", - "pem": "1.12.0", + "pem": "1.12.2", "reflect-metadata": "0.1.10", "rxjs": "5.4.3", "ts-md5": "1.2.2", @@ -130,12 +130,12 @@ "ajv": "5.2.3", "ajv-keywords": "2.1.0", "angular2-template-loader": "0.6.2", - "autoprefixer": "7.1.4", + "autoprefixer": "7.1.5", "awesome-typescript-loader": "3.2.3", "caniuse-lite": "1.0.30000697", - "codelyzer": "3.2.0", + "codelyzer": "3.2.1", "compression-webpack-plugin": "1.0.1", - "copy-webpack-plugin": "4.1.0", + "copy-webpack-plugin": "4.1.1", "coveralls": "3.0.0", "css-loader": "0.28.7", "deep-freeze": "0.0.1", @@ -144,7 +144,7 @@ "imports-loader": "0.7.1", "istanbul-instrumenter-loader": "3.0.0", "jasmine-core": "2.8.0", - "jasmine-marbles": "0.1.0", + "jasmine-marbles": "0.2.0", "jasmine-spec-reporter": "4.2.1", "json-loader": "0.5.7", "karma": "1.7.1", @@ -159,7 +159,7 @@ "karma-remap-istanbul": "0.6.0", "karma-sourcemap-loader": "0.3.7", "karma-webdriver-launcher": "1.0.5", - "karma-webpack": "2.0.4", + "karma-webpack": "2.0.5", "ngrx-store-freeze": "0.2.0", "node-sass": "4.5.3", "nodemon": "1.12.1", @@ -189,7 +189,7 @@ "ts-helpers": "1.1.2", "ts-node": "3.3.0", "tslint": "5.7.0", - "typedoc": "0.8.0", + "typedoc": "0.9.0", "typescript": "2.5.3", "webpack": "3.6.0", "webpack-bundle-analyzer": "2.9.0", diff --git a/yarn.lock b/yarn.lock index 054ae414d5..109d1eb310 100644 --- a/yarn.lock +++ b/yarn.lock @@ -150,42 +150,35 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/fs-extra@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.2.tgz#7b9b1bbf85962cbe029b5a83c9b530d7c75af3ba" +"@types/fs-extra@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.0.tgz#1dd742ad5c9bce308f7a52d02ebc01421bc9102f" dependencies: "@types/node" "*" -"@types/glob@*": - version "5.0.32" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.32.tgz#aec5cfe987c72f099fdb1184452986aa506d5e8f" - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/hammerjs@2.0.35": version "2.0.35" resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.35.tgz#7b7c950c7d54593e23bffc8d2b4feba9866a7277" -"@types/handlebars@^4.0.31": - version "4.0.36" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.36.tgz#ff57c77fa1ab6713bb446534ddc4d979707a3a79" +"@types/handlebars@4.0.31": + version "4.0.31" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.31.tgz#a7fba66fafe42713aee88eeca8db91192efe6e72" -"@types/highlight.js@^9.1.8": - version "9.1.10" - resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.1.10.tgz#b621f809cd9573b80992b90cffc5788208e3069c" +"@types/highlight.js@9.1.8": + version "9.1.8" + resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.1.8.tgz#d227f18bcb8f3f187e16965f2444859a04689758" "@types/jasmine@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.6.0.tgz#997b41a27752b4850af2683bc4a8d8222c25bd02" -"@types/lodash@^4.14.37": - version "4.14.76" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.76.tgz#87874f766774d54e89589697340be9496fb8bf70" +"@types/lodash@4.14.74": + version "4.14.74" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.74.tgz#ac3bd8db988e7f7038e5d22bd76a7ba13f876168" -"@types/marked@0.0.28": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" +"@types/marked@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524" "@types/memory-cache@0.0.31": version "0.0.31" @@ -195,7 +188,7 @@ version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" -"@types/minimatch@*", "@types/minimatch@^2.0.29": +"@types/minimatch@2.0.29": version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" @@ -222,11 +215,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/shelljs@^0.7.0": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.4.tgz#137b5f31306eaff4de120ffe5b9d74b297809cfc" +"@types/shelljs@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.0.tgz#229c157c6bc1e67d6b990e6c5e18dbd2ff58cff0" dependencies: - "@types/glob" "*" "@types/node" "*" "@types/source-map@0.5.1": @@ -576,15 +568,15 @@ atob@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" -autoprefixer@7.1.4, autoprefixer@^7.1.1: - version "7.1.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" +autoprefixer@7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.5.tgz#d65d14b83c7cd1dd7bc801daa00557addf5a06b2" dependencies: - browserslist "^2.4.0" - caniuse-lite "^1.0.30000726" + browserslist "^2.5.0" + caniuse-lite "^1.0.30000744" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^6.0.11" + postcss "^6.0.13" postcss-value-parser "^3.2.3" autoprefixer@^6.3.1: @@ -598,6 +590,17 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" +autoprefixer@^7.1.1: + version "7.1.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" + dependencies: + browserslist "^2.4.0" + caniuse-lite "^1.0.30000726" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^6.0.11" + postcss-value-parser "^3.2.3" + awesome-typescript-loader@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.2.3.tgz#aa2119b7c808a031e2b28945b031450a8975367f" @@ -790,14 +793,14 @@ blocking-proxy@0.0.5: dependencies: minimist "^1.2.0" -bluebird@^2.10.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - bluebird@^3.3.0, bluebird@^3.4.7: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" +bluebird@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -979,6 +982,13 @@ browserslist@^2.0.0, browserslist@^2.4.0: caniuse-lite "^1.0.30000718" electron-to-chromium "^1.3.18" +browserslist@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.5.1.tgz#68e4bc536bbcc6086d62843a2ffccea8396821c6" + dependencies: + caniuse-lite "^1.0.30000744" + electron-to-chromium "^1.3.24" + buffer-crc32@^0.2.1: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1093,6 +1103,10 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726: version "1.0.30000740" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" +caniuse-lite@^1.0.30000744: + version "1.0.30000745" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000745.tgz#20d6fede1157a4935133502946fc7e0e6b880da5" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -1245,9 +1259,9 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -codelyzer@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.2.0.tgz#68eb0a67771ea73006b517053c3035c1838abf14" +codelyzer@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.2.1.tgz#5b1ac75f7e0eb04647842ee29a322bf2167e7229" dependencies: app-root-path "^2.0.1" css-selector-tokenizer "^0.7.0" @@ -1472,17 +1486,17 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -copy-webpack-plugin@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.0.tgz#292a040318fe8ae3b1d7996ef05dfb483eb0b647" +copy-webpack-plugin@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.1.tgz#53ae69e04955ebfa9fda411f54cbb968531d71fd" dependencies: - bluebird "^2.10.2" - fs-extra "^0.26.4" - glob "^6.0.4" - is-glob "^3.1.0" + bluebird "^3.5.1" + fs-extra "^4.0.2" + glob "^7.1.2" + is-glob "^4.0.0" loader-utils "^0.2.15" lodash "^4.3.0" - minimatch "^3.0.0" + minimatch "^3.0.4" node-dir "^0.1.10" core-js@2.5.1, core-js@^2.2.0, core-js@^2.4.0: @@ -2028,7 +2042,7 @@ ejs@^2.5.6: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18, electron-to-chromium@^1.3.24: version "1.3.24" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" @@ -2666,17 +2680,7 @@ fs-extra@^0.22.1: jsonfile "^2.1.0" rimraf "^2.2.8" -fs-extra@^0.26.4: - version "0.26.7" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^4.0.0, fs-extra@^4.0.1: +fs-extra@^4.0.0, fs-extra@^4.0.1, fs-extra@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" dependencies: @@ -2802,17 +2806,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -3430,7 +3424,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3462,6 +3456,12 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -3665,11 +3665,11 @@ jasmine-core@2.8.0, jasmine-core@~2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" -jasmine-marbles@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.1.0.tgz#c9ecdc64e20b6cf55b49a10201a5be33907dadcc" +jasmine-marbles@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.2.0.tgz#b893d8508b75790b634876d3a1bea1345d65c156" dependencies: - lodash.isequal "^4.5.0" + lodash "^4.5.0" jasmine-spec-reporter@4.2.1: version "4.2.1" @@ -3867,9 +3867,9 @@ karma-webdriver-launcher@1.0.5: dependencies: wd "^1.0.0" -karma-webpack@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b" +karma-webpack@2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.5.tgz#4f56887e32cf4f9583391c2388415de06af06efd" dependencies: async "~0.9.0" loader-utils "^0.2.5" @@ -4131,10 +4131,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -5133,9 +5129,9 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pem@1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.0.tgz#603d8207b9b5f83225e37ffcc36268c3aa3fecf3" +pem@1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.2.tgz#d2f1744c9ff8144f795f96d0c54a4b2be6f43b0c" dependencies: md5 "^2.2.1" os-tmpdir "^1.0.1" @@ -5759,7 +5755,7 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@6.0.13: +postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f" dependencies: @@ -5776,14 +5772,6 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: - version "6.0.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.12.tgz#6b0155089d2d212f7bd6a0cecd4c58c007403535" - dependencies: - chalk "^2.1.0" - source-map "^0.5.7" - supports-color "^4.4.0" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -6873,10 +6861,14 @@ source-map-url@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" -source-map@0.5.x, source-map@>=0.5.6, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@0.5.x, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@>=0.5.6, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + source-map@^0.1.38, source-map@^0.1.41, source-map@~0.1.33: version "0.1.43" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" @@ -6889,10 +6881,6 @@ source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - source-map@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" @@ -6958,11 +6946,7 @@ split@0.3: dependencies: through "2" -sprintf-js@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - -sprintf-js@~1.0.2: +sprintf-js@^1.0.3, sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -7409,17 +7393,17 @@ typedoc-default-themes@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" -typedoc@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.8.0.tgz#d7172bc6a29964f451b7609c005beadadefe2361" +typedoc@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.9.0.tgz#159bff7c7784ce5b91d86f3e4cc8928e62040957" dependencies: - "@types/fs-extra" "^4.0.0" - "@types/handlebars" "^4.0.31" - "@types/highlight.js" "^9.1.8" - "@types/lodash" "^4.14.37" - "@types/marked" "0.0.28" - "@types/minimatch" "^2.0.29" - "@types/shelljs" "^0.7.0" + "@types/fs-extra" "4.0.0" + "@types/handlebars" "4.0.31" + "@types/highlight.js" "9.1.8" + "@types/lodash" "4.14.74" + "@types/marked" "0.3.0" + "@types/minimatch" "2.0.29" + "@types/shelljs" "0.7.0" fs-extra "^4.0.0" handlebars "^4.0.6" highlight.js "^9.0.0" From 8c4c533203971a639d4f5befc38c27ff6bfb73a9 Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 9 Oct 2017 08:18:28 -0500 Subject: [PATCH 03/15] include all modules in coverage --- spec-bundle.js | 26 ++------- src/app/app.reducer.ts | 34 +++++------ src/main.browser.ts | 62 ++++++++++----------- src/main.server.ts | 2 +- src/{ => modules}/app/browser-app.module.ts | 14 ++--- src/{ => modules}/app/server-app.module.ts | 18 +++--- src/tsconfig.browser.json | 2 +- src/tsconfig.server.json | 2 +- src/tsconfig.test.json | 2 +- webpack/webpack.test.js | 3 +- 10 files changed, 75 insertions(+), 90 deletions(-) rename src/{ => modules}/app/browser-app.module.ts (76%) rename src/{ => modules}/app/server-app.module.ts (74%) diff --git a/spec-bundle.js b/spec-bundle.js index 36026d530f..d2e4b2fd3e 100644 --- a/spec-bundle.js +++ b/spec-bundle.js @@ -38,25 +38,11 @@ testing.TestBed.initTestEnvironment( browser.platformBrowserDynamicTesting() ); -/* - * Ok, this is kinda crazy. We can use the context method on - * require that webpack created in order to tell webpack - * what files we actually want to require or import. - * Below, context will be a function/object with file names as keys. - * Using that regex we are saying look in ../src then find - * any file that ends with spec.ts and get its path. By passing in true - * we say do this recursively - */ -var testContext = require.context('./src', true, /\.spec\.ts/); -/* - * get all the files, for each file, call the context function - * that will require the file and load it up here. Context will - * loop and require those spec files here - */ -function requireAll(requireContext) { - return requireContext.keys().map(requireContext); -} +var tests = require.context('./src', true, /\.spec\.ts$/); -// requires and returns all modules that match -var modules = requireAll(testContext); +tests.keys().forEach(tests); + +const components = require.context('./src/app', true, /\.module\.ts$/); + +components.keys().forEach(components); diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 646d7a26b5..baa3250549 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,17 +1,17 @@ -import { ActionReducerMap } from '@ngrx/store'; -import * as fromRouter from '@ngrx/router-store'; - -import { headerReducer, HeaderState } from './header/header.reducer'; -import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; - -export interface AppState { - router: fromRouter.RouterReducerState; - hostWindow: HostWindowState; - header: HeaderState; -} - -export const appReducers: ActionReducerMap = { - router: fromRouter.routerReducer, - hostWindow: hostWindowReducer, - header: headerReducer -}; +import { ActionReducerMap } from '@ngrx/store'; +import * as fromRouter from '@ngrx/router-store'; + +import { headerReducer, HeaderState } from './header/header.reducer'; +import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; + +export interface AppState { + router: fromRouter.RouterReducerState; + hostWindow: HostWindowState; + header: HeaderState; +} + +export const appReducers: ActionReducerMap = { + router: fromRouter.routerReducer, + hostWindow: hostWindowReducer, + header: headerReducer +}; diff --git a/src/main.browser.ts b/src/main.browser.ts index 3f228b9d13..55e346108f 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -1,31 +1,31 @@ -import 'zone.js/dist/zone'; -import 'reflect-metadata'; - -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { bootloader } from '@angularclass/bootloader'; - -import { load as loadWebFont } from 'webfontloader'; - -import { BrowserAppModule } from './app/browser-app.module'; - -import { ENV_CONFIG } from './config'; - -if (ENV_CONFIG.production) { - enableProdMode(); -} - -export function main() { - // Load fonts async - // https://github.com/typekit/webfontloader#configuration - loadWebFont({ - google: { - families: ['Droid Sans'] - } - }); - - return platformBrowserDynamic().bootstrapModule(BrowserAppModule); -} - -// support async tag or hmr -bootloader(main); +import 'zone.js/dist/zone'; +import 'reflect-metadata'; + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { bootloader } from '@angularclass/bootloader'; + +import { load as loadWebFont } from 'webfontloader'; + +import { BrowserAppModule } from './modules/app/browser-app.module'; + +import { ENV_CONFIG } from './config'; + +if (ENV_CONFIG.production) { + enableProdMode(); +} + +export function main() { + // Load fonts async + // https://github.com/typekit/webfontloader#configuration + loadWebFont({ + google: { + families: ['Droid Sans'] + } + }); + + return platformBrowserDynamic().bootstrapModule(BrowserAppModule); +} + +// support async tag or hmr +bootloader(main); diff --git a/src/main.server.ts b/src/main.server.ts index f787f30514..aae5b89a62 100644 --- a/src/main.server.ts +++ b/src/main.server.ts @@ -17,7 +17,7 @@ import { enableProdMode } from '@angular/core'; import { ngExpressEngine } from '@nguniversal/express-engine'; -import { ServerAppModule } from './app/server-app.module'; +import { ServerAppModule } from './modules/app/server-app.module'; import { serverApi, createMockApi } from './backend/api'; diff --git a/src/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts similarity index 76% rename from src/app/browser-app.module.ts rename to src/modules/app/browser-app.module.ts index 0904ba9ecc..aad1af9f4e 100644 --- a/src/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -11,15 +11,15 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { EffectsModule } from '@ngrx/effects'; -import { TransferState } from '../modules/transfer-state/transfer-state'; -import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; -import { BrowserTransferStoreEffects } from '../modules/transfer-store/browser-transfer-store.effects'; -import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module'; +import { TransferState } from '../transfer-state/transfer-state'; +import { BrowserTransferStateModule } from '../transfer-state/browser-transfer-state.module'; +import { BrowserTransferStoreEffects } from '../transfer-store/browser-transfer-store.effects'; +import { BrowserTransferStoreModule } from '../transfer-store/browser-transfer-store.module'; -import { AppModule } from './app.module'; -import { CoreModule } from './core/core.module'; +import { AppModule } from '../../app/app.module'; +import { CoreModule } from '../../app/core/core.module'; -import { AppComponent } from './app.component'; +import { AppComponent } from '../../app/app.component'; export function init(cache: TransferState) { return () => { diff --git a/src/app/server-app.module.ts b/src/modules/app/server-app.module.ts similarity index 74% rename from src/app/server-app.module.ts rename to src/modules/app/server-app.module.ts index 0eea0cac24..d97c13a7bb 100644 --- a/src/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -16,21 +16,21 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; -import { TranslateUniversalLoader } from '../modules/translate-universal-loader'; +import { TranslateUniversalLoader } from '../translate-universal-loader'; -import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module'; -import { TransferState } from '../modules/transfer-state/transfer-state'; +import { ServerTransferStateModule } from '../transfer-state/server-transfer-state.module'; +import { TransferState } from '../transfer-state/transfer-state'; -import { ServerTransferStoreEffects } from '../modules/transfer-store/server-transfer-store.effects'; -import { ServerTransferStoreModule } from '../modules/transfer-store/server-transfer-store.module'; +import { ServerTransferStoreEffects } from '../transfer-store/server-transfer-store.effects'; +import { ServerTransferStoreModule } from '../transfer-store/server-transfer-store.module'; -import { AppState } from './app.reducer'; +import { AppState } from '../../app/app.reducer'; -import { AppModule } from './app.module'; +import { AppModule } from '../../app/app.module'; -import { AppComponent } from './app.component'; +import { AppComponent } from '../../app/app.component'; -import { GLOBAL_CONFIG, GlobalConfig } from '../config'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../config'; export function boot(cache: TransferState, appRef: ApplicationRef, store: Store, request: Request, config: GlobalConfig) { // authentication mechanism goes here diff --git a/src/tsconfig.browser.json b/src/tsconfig.browser.json index a14de3972a..f7140b9fe4 100644 --- a/src/tsconfig.browser.json +++ b/src/tsconfig.browser.json @@ -1,6 +1,6 @@ { "extends": "../tsconfig.json", "angularCompilerOptions": { - "entryModule": "./app/browser-app.module#BrowserAppModule" + "entryModule": "./modules/app/browser-app.module#BrowserAppModule" } } diff --git a/src/tsconfig.server.json b/src/tsconfig.server.json index b86ab358f9..480b685a2a 100644 --- a/src/tsconfig.server.json +++ b/src/tsconfig.server.json @@ -1,6 +1,6 @@ { "extends": "../tsconfig.json", "angularCompilerOptions": { - "entryModule": "./app/server-app.module#ServerAppModule" + "entryModule": "./modules/app/server-app.module#ServerAppModule" } } diff --git a/src/tsconfig.test.json b/src/tsconfig.test.json index 32a466a3e9..712ad1ab1c 100644 --- a/src/tsconfig.test.json +++ b/src/tsconfig.test.json @@ -4,6 +4,6 @@ "sourceMap": true }, "angularCompilerOptions": { - "entryModule": "./app/browser-app.module#BrowserAppModule" + "entryModule": "./modules/app/browser-app.module#BrowserAppModule" } } diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js index 818e348c7e..703da033a2 100644 --- a/webpack/webpack.test.js +++ b/webpack/webpack.test.js @@ -225,8 +225,7 @@ module.exports = function (options) { new ContextReplacementPlugin( /angular(\\|\/)core(\\|\/)@angular/, - root('./src'), - {} + root('./src'), {} ), /** From 7860951a337c49da06ae19582cc59291647b06c9 Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 9 Oct 2017 08:30:21 -0500 Subject: [PATCH 04/15] minor refactoring and some formatting of karma config --- karma.conf.js | 33 +++++++++++++++++++++------------ spec-bundle.js | 6 +++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 8f51e61344..e43191d8ee 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -27,18 +27,18 @@ module.exports = function (config) { frameworks: ['jasmine'], plugins: [ - require('karma-webpack'), - require('karma-jasmine'), + require("istanbul-instrumenter-loader"), require('karma-chrome-launcher'), - require('karma-phantomjs-launcher'), - require('karma-webdriver-launcher'), require('karma-coverage'), - require('karma-remap-coverage'), + require("karma-istanbul-preprocessor"), + require('karma-jasmine'), require('karma-mocha-reporter'), + require('karma-phantomjs-launcher'), + require('karma-remap-coverage'), require('karma-remap-istanbul'), require('karma-sourcemap-loader'), - require("istanbul-instrumenter-loader"), - require("karma-istanbul-preprocessor") + require('karma-webdriver-launcher'), + require('karma-webpack') ], // list of files to exclude @@ -59,7 +59,11 @@ module.exports = function (config) { * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor */ preprocessors: { - './spec-bundle.js': ['istanbul', 'webpack', 'sourcemap'] + './spec-bundle.js': [ + 'istanbul', + 'webpack', + 'sourcemap' + ] }, // Webpack Config at ./webpack.test.js @@ -79,9 +83,9 @@ module.exports = function (config) { remapIstanbulReporter: { remapOptions: {}, //additional remap options reports: { - json: 'coverage/coverage.json', - lcovonly: 'coverage/lcov.info', - html: 'coverage/html/', + json: './coverage/coverage.json', + lcovonly: './coverage/lcov.info', + html: './coverage/html/', } }, @@ -111,7 +115,12 @@ module.exports = function (config) { * possible values: 'dots', 'progress' * available reporters: https://npmjs.org/browse/keyword/karma-reporter */ - reporters: ['mocha', 'coverage', 'remap-coverage', 'karma-remap-istanbul'], + reporters: [ + 'mocha', + 'coverage', + 'remap-coverage', + 'karma-remap-istanbul' + ], // Karma web server port port: 9876, diff --git a/spec-bundle.js b/spec-bundle.js index d2e4b2fd3e..b9df9bec5e 100644 --- a/spec-bundle.js +++ b/spec-bundle.js @@ -38,11 +38,11 @@ testing.TestBed.initTestEnvironment( browser.platformBrowserDynamicTesting() ); - var tests = require.context('./src', true, /\.spec\.ts$/); tests.keys().forEach(tests); -const components = require.context('./src/app', true, /\.module\.ts$/); +// includes all modules into test coverage +const modules = require.context('./src/app', true, /\.module\.ts$/); -components.keys().forEach(components); +modules.keys().forEach(modules); From f4c0df176ef5590dae0a0fea595e7079c55a63c7 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 19:08:03 -0500 Subject: [PATCH 05/15] metadata service --- package.json | 49 ++- .../collection-page-routing.module.ts | 7 +- .../community-page-routing.module.ts | 7 +- .../+item-page/item-page-routing.module.ts | 5 +- .../search-page.component.spec.ts | 2 +- src/app/app.component.spec.ts | 8 +- src/app/app.component.ts | 9 +- src/app/core/core.module.ts | 43 ++- src/app/core/data/data.service.ts | 7 +- .../core/metadata/metadata.service.spec.ts | 114 ++++++ src/app/core/metadata/metadata.service.ts | 354 ++++++++++++++++++ src/app/footer/footer.component.spec.ts | 2 +- src/app/shared/error/error.component.spec.ts | 6 +- .../shared/loading/loading.component.spec.ts | 6 +- src/app/shared/mocks/mock-action.ts | 6 + src/app/shared/mocks/mock-active-router.ts | 34 ++ .../shared/mocks/mock-host-window-service.ts | 19 + src/app/shared/mocks/mock-item.ts | 164 ++++++++ src/app/shared/mocks/mock-metadata-service.ts | 9 + src/app/shared/mocks/mock-normalized-item.ts | 114 ++++++ src/app/shared/mocks/mock-router.ts | 4 + src/app/shared/mocks/mock-store.ts | 23 ++ src/app/shared/mocks/mock-translate-loader.ts | 8 + .../pagination/pagination.component.spec.ts | 22 +- yarn.lock | 166 ++++---- 25 files changed, 1018 insertions(+), 170 deletions(-) create mode 100644 src/app/core/metadata/metadata.service.spec.ts create mode 100644 src/app/core/metadata/metadata.service.ts create mode 100644 src/app/shared/mocks/mock-action.ts create mode 100644 src/app/shared/mocks/mock-active-router.ts create mode 100644 src/app/shared/mocks/mock-host-window-service.ts create mode 100644 src/app/shared/mocks/mock-item.ts create mode 100644 src/app/shared/mocks/mock-metadata-service.ts create mode 100644 src/app/shared/mocks/mock-normalized-item.ts create mode 100644 src/app/shared/mocks/mock-router.ts create mode 100644 src/app/shared/mocks/mock-store.ts create mode 100644 src/app/shared/mocks/mock-translate-loader.ts diff --git a/package.json b/package.json index 6f761ef47c..9a5e024941 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,14 @@ }, "scripts": { "global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup", - "clean:coverage": "yarn run rimraf coverage", - "clean:dist": "yarn run rimraf dist", - "clean:doc": "yarn run rimraf doc", - "clean:log": "yarn run rimraf *.log*", - "clean:json": "yarn run rimraf *.records.json", - "clean:node": "yarn run rimraf node_modules", + "clean:coverage": "rimraf coverage", + "clean:dist": "rimraf dist", + "clean:doc": "rimraf doc", + "clean:log": "rimraf *.log*", + "clean:json": "rimraf *.records.json", + "clean:node": "rimraf node_modules", "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json", "clean": "yarn run clean:prod && yarn run clean:node", - "rimraf": "rimraf", "prebuild": "yarn run clean:dist", "prebuild:aot": "yarn run prebuild", "prebuild:prod": "yarn run prebuild", @@ -69,15 +68,15 @@ "coverage": "http-server -c-1 -o -p 9875 ./coverage" }, "dependencies": { - "@angular/animations": "4.4.4", - "@angular/common": "4.4.4", - "@angular/core": "4.4.4", - "@angular/forms": "4.4.4", - "@angular/http": "4.4.4", - "@angular/platform-browser": "4.4.4", - "@angular/platform-browser-dynamic": "4.4.4", - "@angular/platform-server": "4.4.4", - "@angular/router": "4.4.4", + "@angular/animations": "4.4.5", + "@angular/common": "4.4.5", + "@angular/core": "4.4.5", + "@angular/forms": "4.4.5", + "@angular/http": "4.4.5", + "@angular/platform-browser": "4.4.5", + "@angular/platform-browser-dynamic": "4.4.5", + "@angular/platform-server": "4.4.5", + "@angular/router": "4.4.5", "@angularclass/bootloader": "1.0.1", "@angularclass/idle-preload": "1.0.4", "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.5", @@ -89,11 +88,11 @@ "@ngx-translate/http-loader": "2.0.0", "body-parser": "1.18.2", "bootstrap": "v4.0.0-beta", - "cerialize": "0.1.16", + "cerialize": "0.1.18", "compression": "1.7.1", "cookie-parser": "1.4.3", "core-js": "2.5.1", - "express": "4.16.1", + "express": "4.16.2", "express-session": "1.15.6", "font-awesome": "4.7.0", "http-server": "0.10.0", @@ -103,7 +102,7 @@ "methods": "1.1.2", "morgan": "1.9.0", "ngx-pagination": "3.0.1", - "pem": "1.12.2", + "pem": "1.12.3", "reflect-metadata": "0.1.10", "rxjs": "5.4.3", "ts-md5": "1.2.2", @@ -111,10 +110,10 @@ "zone.js": "0.8.18" }, "devDependencies": { - "@angular/compiler": "4.4.4", - "@angular/compiler-cli": "4.4.4", + "@angular/compiler": "4.4.5", + "@angular/compiler-cli": "4.4.5", "@ngrx/store-devtools": "4.0.0", - "@ngtools/webpack": "1.7.2", + "@ngtools/webpack": "1.7.4", "@types/cookie-parser": "1.4.1", "@types/deep-freeze": "0.1.1", "@types/express": "4.0.37", @@ -168,13 +167,13 @@ "postcss-apply": "0.8.0", "postcss-cli": "4.1.1", "postcss-cssnext": "3.0.2", - "postcss-loader": "2.0.6", + "postcss-loader": "2.0.7", "postcss-responsive-type": "1.0.0", "postcss-smart-import": "0.7.5", "protractor": "5.1.2", "protractor-istanbul-plugin": "2.0.0", "raw-loader": "0.5.1", - "resolve-url-loader": "2.1.0", + "resolve-url-loader": "2.1.1", "rimraf": "2.6.2", "rollup": "0.50.0", "rollup-plugin-commonjs": "8.2.1", @@ -191,7 +190,7 @@ "tslint": "5.7.0", "typedoc": "0.9.0", "typescript": "2.5.3", - "webpack": "3.6.0", + "webpack": "3.7.1", "webpack-bundle-analyzer": "2.9.0", "webpack-dev-middleware": "1.12.0", "webpack-dev-server": "2.9.1", diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index c62643acc2..fc7f58b6fb 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,12 +2,15 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; +import { NormalizedCollection } from '../core/cache/models/normalized-collection.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } + { path: ':id', component: CollectionPageComponent, pathMatch: 'full', data: { type: NormalizedCollection } } ]) ] }) -export class CollectionPageRoutingModule { } +export class CollectionPageRoutingModule { + +} diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e090a93087..e761717808 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,12 +2,15 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; +import { NormalizedCommunity } from '../core/cache/models/normalized-community.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } + { path: ':id', component: CommunityPageComponent, pathMatch: 'full', data: { type: NormalizedCommunity } } ]) ] }) -export class CommunityPageRoutingModule { } +export class CommunityPageRoutingModule { + +} diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 9dca4d0f6e..e315b18a9c 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -3,12 +3,13 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; +import { NormalizedItem } from '../core/cache/models/normalized-item.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: ItemPageComponent, pathMatch: 'full' }, - { path: ':id/full', component: FullItemPageComponent } + { path: ':id', component: ItemPageComponent, pathMatch: 'full', data: { type: NormalizedItem } }, + { path: ':id/full', component: FullItemPageComponent, data: { type: NormalizedItem } } ]) ] }) diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index a3e314db69..0b00021ed6 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -104,7 +104,7 @@ describe('SearchPageComponent', () => { (comp as any).updateSearchResults({}); expect(comp.results as any).toBe(mockResults); - }); + }); }); }); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 99cf1068e4..0854c5756e 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,4 +1,3 @@ -// ... test imports import { async, ComponentFixture, @@ -23,14 +22,18 @@ import { AppComponent } from './app.component'; import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowResizeAction } from './shared/host-window.actions'; -import { MockTranslateLoader } from './shared/testing/mock-translate-loader'; import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module'; +import { MetadataService } from './core/metadata/metadata.service'; + import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; +import { MockMetadataService } from './shared/mocks/mock-metadata-service'; + let comp: AppComponent; let fixture: ComponentFixture; let de: DebugElement; @@ -57,6 +60,7 @@ describe('App component', () => { providers: [ { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, { provide: NativeWindowService, useValue: new NativeWindowRef() }, + { provide: MetadataService, useValue: new MockMetadataService() }, AppComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9ea58365df..221c1c37d1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,12 +12,10 @@ import { TranslateService } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { TransferState } from '../modules/transfer-state/transfer-state'; - import { HostWindowState } from './shared/host-window.reducer'; - import { HostWindowResizeAction } from './shared/host-window.actions'; - import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MetadataService } from './core/metadata/metadata.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../config'; @@ -35,13 +33,16 @@ export class AppComponent implements OnInit { @Inject(NativeWindowService) private _window: NativeWindowRef, private translate: TranslateService, private cache: TransferState, - private store: Store + private store: Store, + private metadata: MetadataService ) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); // the lang to use, if the lang isn't available, it will use the current loader to get them translate.use('en'); + metadata.listenForRouteChange(); + if (config.debug) { console.info(config); } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 9742a6b500..b782f1d4fc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,29 +1,35 @@ -import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core'; +import { + NgModule, + Optional, + SkipSelf, + ModuleWithProviders +} from '@angular/core'; import { CommonModule } from '@angular/common'; -import { isNotEmpty } from '../shared/empty.util'; -import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; -import { ObjectCacheService } from './cache/object-cache.service'; -import { ResponseCacheService } from './cache/response-cache.service'; -import { CollectionDataService } from './data/collection-data.service'; -import { ItemDataService } from './data/item-data.service'; -import { RequestService } from './data/request.service'; -import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; -import { CommunityDataService } from './data/community-data.service'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { coreEffects } from './core.effects'; -import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { coreEffects } from './core.effects'; import { coreReducers } from './core.reducers'; -import { DSOResponseParsingService } from './data/dso-response-parsing.service'; -import { RootResponseParsingService } from './data/root-response-parsing.service'; + +import { isNotEmpty } from '../shared/empty.util'; import { ApiService } from '../shared/api.service'; - +import { CollectionDataService } from './data/collection-data.service'; +import { CommunityDataService } from './data/community-data.service'; +import { DSOResponseParsingService } from './data/dso-response-parsing.service'; +import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; import { HostWindowService } from '../shared/host-window.service'; -import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; - +import { ItemDataService } from './data/item-data.service'; +import { MetadataService } from './metadata/metadata.service'; +import { ObjectCacheService } from './cache/object-cache.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; +import { RequestService } from './data/request.service'; +import { ResponseCacheService } from './cache/response-cache.service'; +import { RootResponseParsingService } from './data/root-response-parsing.service'; import { ServerResponseService } from '../shared/server-response.service'; +import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; const IMPORTS = [ CommonModule, @@ -47,6 +53,7 @@ const PROVIDERS = [ DSpaceRESTv2Service, HostWindowService, ItemDataService, + MetadataService, ObjectCacheService, PaginationComponentOptions, RemoteDataBuildService, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index e48e7a8bb8..d1054d69ef 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -3,7 +3,10 @@ import { CacheableObject } from '../cache/object-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { RemoteData } from './remote-data'; import { - FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest, + FindAllOptions, + FindAllRequest, + FindByIDRequest, + RestRequest, RootEndpointRequest } from './request.models'; import { Store } from '@ngrx/store'; @@ -51,7 +54,7 @@ export abstract class DataService } public isEnabledOnRestApi(): Observable { - return this.getEndpointMap() + return this.getEndpointMap() .map((map: EndpointMap) => isNotEmpty(map[this.linkName])) .startWith(undefined) .distinctUntilChanged(); diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts new file mode 100644 index 0000000000..6e76874604 --- /dev/null +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -0,0 +1,114 @@ +import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { Location, CommonModule } from '@angular/common'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By, Meta } from '@angular/platform-browser'; +import { Router } from '@angular/router'; + +import { Store, StoreModule } from '@ngrx/store'; + +import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from './metadata.service'; + +import { CoreState } from '../core.reducers'; + +import { GlobalConfig } from '../../../config/global-config.interface'; +import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; + +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RequestService } from '../data/request.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +import { NormalizedItem } from '../cache/models/normalized-item.model'; + +import { MockRouter } from '../../shared/mocks/mock-router'; +import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; +import { MockItem } from '../../shared/mocks/mock-item'; + +/* tslint:disable:max-classes-per-file */ +@Component({ + template: `` +}) +class TestComponent { + constructor(private metadata: MetadataService) { + metadata.listenForRouteChange(); + } +} + +@Component({ template: '' }) class DummyItemComponent { } +/* tslint:enable:max-classes-per-file */ + +describe('MetadataService', () => { + let metadataService: MetadataService; + + let meta: Meta; + + let store: Store; + + let objectCacheService: ObjectCacheService; + let responseCacheService: ResponseCacheService; + let requestService: RequestService; + let remoteDataBuildService: RemoteDataBuildService; + + let location: Location; + let router: Router; + let fixture: ComponentFixture; + + beforeEach(() => { + + store = new Store(undefined, undefined, undefined); + spyOn(store, 'dispatch'); + + objectCacheService = new ObjectCacheService(store); + responseCacheService = new ResponseCacheService(store); + requestService = new RequestService(objectCacheService, responseCacheService, store); + remoteDataBuildService = new RemoteDataBuildService(objectCacheService, responseCacheService, requestService); + + TestBed.configureTestingModule({ + imports: [ + CommonModule, + StoreModule.forRoot({}), + RouterTestingModule.withRoutes([ + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } } + ]) + ], + declarations: [ + TestComponent, + DummyItemComponent + ], + providers: [ + { provide: ObjectCacheService, useValue: objectCacheService }, + { provide: ResponseCacheService, useValue: responseCacheService }, + { provide: RequestService, useValue: requestService }, + { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, + { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, + Meta, + MetadataService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }); + meta = TestBed.get(Meta); + metadataService = TestBed.get(MetadataService); + + router = TestBed.get(Router); + location = TestBed.get(Location); + + fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + }); + + beforeEach(() => { + spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { + observer.next(MockNormalizedItem); + })); + spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + }); + + it('upon navigation should call meta tag setters', () => { + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + }); + +}); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts new file mode 100644 index 0000000000..9e8c6747f5 --- /dev/null +++ b/src/app/core/metadata/metadata.service.ts @@ -0,0 +1,354 @@ +import 'rxjs/add/operator/first' +import 'rxjs/add/operator/take' + +import { Inject, Injectable } from '@angular/core'; +import { + ActivatedRoute, + Event, + NavigationEnd, + Params, + Router +} from '@angular/router'; +import { Meta, MetaDefinition } from '@angular/platform-browser'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Observable } from 'rxjs/Observable'; + +import { Bitstream } from '../shared/bitstream.model'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { Item } from '../shared/item.model'; +import { Metadatum } from '../shared/metadatum.model'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; + +@Injectable() +export class MetadataService { + + private initialized: boolean; + + private tagStore: Map; + + private currentObject: BehaviorSubject; + + constructor( + private router: Router, + private objectCacheService: ObjectCacheService, + private remoteDataBuildService: RemoteDataBuildService, + private meta: Meta, + @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig + ) { + this.meta.addTags([ + { property: 'og:title', content: 'DSpace Angular Universal' } + ]); + this.initialized = false; + this.tagStore = new Map(); + } + + public listenForRouteChange(): void { + const subscription = this.router.events + .filter((event) => event instanceof NavigationEnd) + .map(() => this.router.routerState.root) + .map((route: ActivatedRoute) => { + route = this.getCurrentRoute(route); + return { params: route.params, data: route.data }; + }).subscribe((routeInfo: any) => { + if (routeInfo.params.value.id && routeInfo.data.value.type) { + this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) + .first().subscribe((normalizedObject: CacheableObject) => { + const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } else { + this.clearMetaTags(); + } + }); + } + + private initialize(dspaceObject: DSpaceObject): void { + this.currentObject = new BehaviorSubject(dspaceObject); + const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { + this.setMetaTags(); + }); + this.initialized = true; + } + + private getCurrentRoute(route: ActivatedRoute): ActivatedRoute { + while (route.firstChild) { + route = route.firstChild; + } + return route; + } + + private setMetaTags(): void { + + this.clearMetaTags(); + + this.setCitationTitleTag(); + this.setCitationAuthorTags(); + this.setCitationDateTag(); + this.setCitationISSNTag(); + this.setCitationISBNTag(); + + this.setCitationLanguageTag(); + this.setCitationKeywordsTag(); + + this.setCitationAbstractUrlTag(); + this.setCitationPdfUrlTag(); + + if (this.isDissertation()) { + this.setCitationDissertationNameTag(); + this.setCitationDissertationInstitutionTag(); + } + + if (this.isTechReport()) { + this.setCitationTechReportInstitutionTag(); + } + + // this.setCitationJournalTitleTag(); + // this.setCitationVolumeTag(); + // this.setCitationIssueTag(); + // this.setCitationFirstPageTag(); + // this.setCitationLastPageTag(); + // this.setCitationDOITag(); + // this.setCitationPMIDTag(); + + // this.setCitationFullTextTag(); + + // this.setCitationConferenceTag(); + + // this.setCitationPatentCountryTag(); + // this.setCitationPatentNumberTag(); + + } + + /** + * Add to the + */ + private setCitationTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_title', value); + } + + /** + * Add to the + */ + private setCitationAuthorTags(): void { + const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']); + this.addMetaTags('citation_author', values); + } + + /** + * Add to the + */ + private setCitationDateTag(): void { + const value = this.getFirstMetaTagValue(['dc.date.copyright', 'dc.date.issued', 'dc.date.available', 'dc.date.accessioned']); + this.addMetaTag('citation_date', value); + } + + /** + * Add to the + */ + private setCitationISSNTag(): void { + const value = this.getMetaTagValue('dc.identifier.issn'); + this.addMetaTag('citation_issn', value); + } + + /** + * Add to the + */ + private setCitationISBNTag(): void { + const value = this.getMetaTagValue('dc.identifier.isbn'); + this.addMetaTag('citation_isbn', value); + } + + /** + * Add to the + */ + private setCitationLanguageTag(): void { + const value = this.getMetaTagValue('dc.language.iso'); + this.addMetaTag('citation_language', value); + } + + /** + * Add to the + */ + private setCitationDissertationNameTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_dissertation_name', value); + } + + /** + * Add to the + */ + private setCitationDissertationInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_dissertation_institution', value); + } + + /** + * Add to the + */ + private setCitationTechReportInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_technical_report_institution', value); + } + + /** + * Add to the + */ + private setCitationKeywordsTag(): void { + const value = this.getMetaTagValuesAndCombine('dc.subject'); + this.addMetaTag('citation_keywords', value); + } + + /** + * Add to the + */ + private setCitationAbstractUrlTag(): void { + if (this.currentObject.value instanceof Item) { + const value = [this.envConfig.ui.baseUrl, this.router.url].join(''); + this.addMetaTag('citation_abstract_html_url', value); + } + } + + /** + * Add to the + */ + private setCitationPdfUrlTag(): void { + if (this.currentObject.value instanceof Item) { + const item = this.currentObject.value as Item; + // NOTE: Observable resolves many times with same data + // taking only two, fist one is empty array + const subscription = item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => { + for (const bitstream of bitstreams) { + if (bitstream.mimetype === 'application/pdf') { + this.addMetaTag('citation_abstract_html_url', bitstream.content); + break; + } + } + }); + } + } + + /** + * Returns true if this._item is a dissertation + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Thesis' + */ + private isDissertation(): boolean { + let isDissertation = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isDissertation = metadatum.value === 'Thesis'; + break; + } + } + return isDissertation; + } + + /** + * Returns true if this._item is a technical report + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Technical Report' + */ + private isTechReport(): boolean { + let isTechReport = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isTechReport = metadatum.value === 'Technical Report'; + break; + } + } + return isTechReport; + } + + private getMetaTagValue(key: string): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === key) { + value = metadatum.value; + } + } + return value; + } + + private getFirstMetaTagValue(keys: string[]): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + value = metadatum.value; + break; + } + } + if (value !== undefined) { + break; + } + } + return value; + } + + private getMetaTagValuesAndCombine(key: string): string { + return this.getMetaTagValues([key]).join('; '); + } + + private getMetaTagValues(keys: string[]): string[] { + const values: string[] = []; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + values.push(metadatum.value); + } + } + } + return values; + } + + private addMetaTag(property: string, content: string): void { + if (content) { + const tag = { property, content } as MetaDefinition; + this.meta.addTag(tag); + this.storeTag(property, tag); + } + } + + private addMetaTags(property: string, content: string[]): void { + for (const value of content) { + this.addMetaTag(property, value); + } + } + + private storeTag(key: string, tag: MetaDefinition): void { + const tags: MetaDefinition[] = this.getTags(key); + tags.push(tag); + this.setTags(key, tags); + } + + private getTags(key: string): MetaDefinition[] { + let tags: MetaDefinition[] = this.tagStore.get(key); + if (tags === undefined) { + tags = []; + } + return tags; + } + + private setTags(key: string, tags: MetaDefinition[]): void { + this.tagStore.set(key, tags); + } + + private clearMetaTags() { + this.tagStore.forEach((tags: MetaDefinition[], property: string) => { + this.meta.removeTag("property='" + property + "'"); + }); + this.tagStore.clear(); + } + +} diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts index 6d56726764..dde432e1ef 100644 --- a/src/app/footer/footer.component.spec.ts +++ b/src/app/footer/footer.component.spec.ts @@ -21,7 +21,7 @@ import { Store, StoreModule } from '@ngrx/store'; // Load the implementations that should be tested import { FooterComponent } from './footer.component'; -import { MockTranslateLoader } from '../shared/testing/mock-translate-loader'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; let comp: FooterComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/error/error.component.spec.ts b/src/app/shared/error/error.component.spec.ts index a0226f7f86..7335f93aed 100644 --- a/src/app/shared/error/error.component.spec.ts +++ b/src/app/shared/error/error.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { ErrorComponent } from './error.component'; @@ -25,8 +25,8 @@ describe('ErrorComponent (inline template)', () => { } }), ], - declarations: [ ErrorComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [ErrorComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/loading/loading.component.spec.ts b/src/app/shared/loading/loading.component.spec.ts index 0b758b8218..aca9673282 100644 --- a/src/app/shared/loading/loading.component.spec.ts +++ b/src/app/shared/loading/loading.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { LoadingComponent } from './loading.component'; @@ -25,8 +25,8 @@ describe('LoadingComponent (inline template)', () => { } }), ], - declarations: [ LoadingComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [LoadingComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/mocks/mock-action.ts b/src/app/shared/mocks/mock-action.ts new file mode 100644 index 0000000000..0f619c8aff --- /dev/null +++ b/src/app/shared/mocks/mock-action.ts @@ -0,0 +1,6 @@ +import { Action } from '@ngrx/store'; + +export class MockAction implements Action { + type = null; + payload: {}; +} diff --git a/src/app/shared/mocks/mock-active-router.ts b/src/app/shared/mocks/mock-active-router.ts new file mode 100644 index 0000000000..391b9c3426 --- /dev/null +++ b/src/app/shared/mocks/mock-active-router.ts @@ -0,0 +1,34 @@ +import { Params } from '@angular/router'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockActivatedRoute { + + private _testParams?: any; + + // ActivatedRoute.params is Observable + private subject?: BehaviorSubject = new BehaviorSubject(this.testParams); + + params = this.subject.asObservable(); + queryParams = this.subject.asObservable(); + + constructor(params?: Params) { + if (params) { + this.testParams = params; + } else { + this.testParams = {}; + } + } + + // Test parameters + get testParams() { return this._testParams; } + set testParams(params: {}) { + this._testParams = params; + this.subject.next(params); + } + + // ActivatedRoute.snapshot.params + get snapshot() { + return { params: this.testParams }; + } +} diff --git a/src/app/shared/mocks/mock-host-window-service.ts b/src/app/shared/mocks/mock-host-window-service.ts new file mode 100644 index 0000000000..104e712682 --- /dev/null +++ b/src/app/shared/mocks/mock-host-window-service.ts @@ -0,0 +1,19 @@ +import { Observable } from 'rxjs/Observable'; + +// declare a stub service +export class MockHostWindowService { + + private width: number; + + constructor(width) { + this.setWidth(width); + } + + setWidth(width) { + this.width = width; + } + + isXs(): Observable { + return Observable.of(this.width < 576); + } +} diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts new file mode 100644 index 0000000000..43ad178485 --- /dev/null +++ b/src/app/shared/mocks/mock-item.ts @@ -0,0 +1,164 @@ +import { Observable } from 'rxjs/Observable'; + +import { Item } from '../../core/shared/item.model'; + +export const MockItem: Item = Object.assign(new Item(), { + handle: '10673/6', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + bitstreams: { + self: { + _isScalar: true, + value: '1507836003548', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next([]); + }) + }, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', + id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + type: 'item', + name: 'Test PowerPoint Document', + metadata: [ + { + key: 'dc.creator', + language: 'en_US', + value: 'Doe, Jane' + }, + { + key: 'dc.date.accessioned', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.available', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.issued', + language: null, + value: '1650-06-26' + }, + { + key: 'dc.identifier.issn', + language: 'en_US', + value: '123456789' + }, + { + key: 'dc.identifier.uri', + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + }, + { + key: 'dc.description.abstract', + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + }, + { + key: 'dc.language', + language: 'en_US', + value: 'en' + }, + { + key: 'dc.rights', + language: 'en_US', + value: '© Jane Doe' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword1' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword2' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword3' + }, + { + key: 'dc.title', + language: 'en_US', + value: 'Test PowerPoint Document' + }, + { + key: 'dc.type', + language: 'en_US', + value: 'text' + } + ], + owningCollection: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next([]); + }) + } +}) diff --git a/src/app/shared/mocks/mock-metadata-service.ts b/src/app/shared/mocks/mock-metadata-service.ts new file mode 100644 index 0000000000..8456e92b92 --- /dev/null +++ b/src/app/shared/mocks/mock-metadata-service.ts @@ -0,0 +1,9 @@ + +export class MockMetadataService { + + // tslint:disable-next-line:no-empty + public listenForRouteChange(): void { + + } + +} diff --git a/src/app/shared/mocks/mock-normalized-item.ts b/src/app/shared/mocks/mock-normalized-item.ts new file mode 100644 index 0000000000..d8716b4bf2 --- /dev/null +++ b/src/app/shared/mocks/mock-normalized-item.ts @@ -0,0 +1,114 @@ +import { NormalizedItem } from '../../core/cache/models/normalized-item.model'; + +export const MockNormalizedItem: NormalizedItem = Object.assign(new NormalizedItem(), { + handle: '10673/6', + lastModified: new Date('2017-04-24T19:44:08.178+0000'), + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + bitstreams: [ + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/a7cd7d97-4e40-41db-80a8-fac908b63bb8', + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/9ff3df0d-1709-472f-8c00-d3e8db2153c8', + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/d660a4b8-e7cc-45cd-b026-35f98c5bd3ba' + ], + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', + id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + type: 'item', + name: 'Test PowerPoint Document', + metadata: [ + { + key: 'dc.creator', + language: 'en_US', + value: 'Doe, Jane L' + }, + { + key: 'dc.date.accessioned', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.available', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.issued', + language: null, + value: '1650-06-26' + }, + { + key: 'dc.identifier.issn', + language: 'en_US', + value: '123456789' + }, + { + key: 'dc.identifier.uri', + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + }, + { + key: 'dc.description.abstract', + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + }, + { + key: 'dc.language', + language: 'en_US', + value: 'en' + }, + { + key: 'dc.rights', + language: 'en_US', + value: '© Jane Doe' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword1' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword2' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword3' + }, + { + key: 'dc.title', + language: 'en_US', + value: 'Test PowerPoint Document' + }, + { + key: 'dc.type', + language: 'en_US', + value: 'text' + } + ], + owningCollection: [ + 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb' + ] +}) diff --git a/src/app/shared/mocks/mock-router.ts b/src/app/shared/mocks/mock-router.ts new file mode 100644 index 0000000000..054c63d4c0 --- /dev/null +++ b/src/app/shared/mocks/mock-router.ts @@ -0,0 +1,4 @@ +export class MockRouter { + // noinspection TypeScriptUnresolvedFunction + navigate = jasmine.createSpy('navigate'); +} diff --git a/src/app/shared/mocks/mock-store.ts b/src/app/shared/mocks/mock-store.ts new file mode 100644 index 0000000000..c619b5aa77 --- /dev/null +++ b/src/app/shared/mocks/mock-store.ts @@ -0,0 +1,23 @@ +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockStore extends BehaviorSubject { + + constructor(private _initialState: T) { + super(_initialState); + } + + dispatch = (action: Action): void => { + console.info(); + } + + select = (pathOrMapFn: any): Observable => { + return Observable.of(this.getValue()); + } + + nextState(_newState: T) { + this.next(_newState); + } + +} diff --git a/src/app/shared/mocks/mock-translate-loader.ts b/src/app/shared/mocks/mock-translate-loader.ts new file mode 100644 index 0000000000..6e22066f8a --- /dev/null +++ b/src/app/shared/mocks/mock-translate-loader.ts @@ -0,0 +1,8 @@ +import { TranslateLoader } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; + +export class MockTranslateLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return Observable.of({}); + } +} diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 92d7ba693f..a4b9e5fcea 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -34,10 +34,10 @@ import Spy = jasmine.Spy; import { PaginationComponent } from './pagination.component'; import { PaginationComponentOptions } from './pagination-component-options.model'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; -import { HostWindowServiceStub } from '../testing/host-window-service-stub'; -import { ActivatedRouteStub } from '../testing/active-router-stub'; -import { RouterStub } from '../testing/router-stub'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; +import { MockHostWindowService } from '../mocks/mock-host-window-service'; +import { MockActivatedRoute } from '../mocks/mock-active-router'; +import { MockRouter } from '../mocks/mock-router'; import { HostWindowService } from '../host-window.service'; import { EnumKeysPipe } from '../utils/enum-keys-pipe'; @@ -45,7 +45,7 @@ import { SortOptions } from '../../core/cache/models/sort-options.model'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config'; -function createTestComponent(html: string, type: { new (...args: any[]): T }): ComponentFixture { +function createTestComponent(html: string, type: { new(...args: any[]): T }): ComponentFixture { TestBed.overrideComponent(type, { set: { template: html } }); @@ -123,19 +123,19 @@ describe('Pagination component', () => { let testFixture: ComponentFixture; let de: DebugElement; let html; - let hostWindowServiceStub: HostWindowServiceStub; + let hostWindowServiceStub: MockHostWindowService; - let activatedRouteStub: ActivatedRouteStub; - let routerStub: RouterStub; + let activatedRouteStub: MockActivatedRoute; + let routerStub: MockRouter; // Define initial state and test state const _initialState = { width: 1600, height: 770 }; // async beforeEach beforeEach(async(() => { - activatedRouteStub = new ActivatedRouteStub(); - routerStub = new RouterStub(); - hostWindowServiceStub = new HostWindowServiceStub(_initialState.width); + activatedRouteStub = new MockActivatedRoute(); + routerStub = new MockRouter(); + hostWindowServiceStub = new MockHostWindowService(_initialState.width); TestBed.configureTestingModule({ imports: [ diff --git a/yarn.lock b/yarn.lock index 109d1eb310..39e4bf575d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,79 +2,79 @@ # yarn lockfile v1 -"@angular/animations@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.4.tgz#a2f9353604347abe15df98292058842f52f08bc2" +"@angular/animations@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.5.tgz#5a5a551d757e5a5560098f6f8535c102d93954d7" dependencies: tslib "^1.7.1" -"@angular/common@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.4.tgz#ae0a818aaa0c6a3f0901e7b80bd94e1c22eb9365" +"@angular/common@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.5.tgz#bd5179dc922adbf4c3ea6dfb19e73cb849ffdc37" dependencies: tslib "^1.7.1" -"@angular/compiler-cli@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.4.tgz#063080a497d9175396825050222c717da184f6cf" +"@angular/compiler-cli@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.5.tgz#61fa0336acd1a208c5f1c5c6d4df679e99953248" dependencies: - "@angular/tsc-wrapped" "4.4.4" + "@angular/tsc-wrapped" "4.4.5" minimist "^1.2.0" reflect-metadata "^0.1.2" -"@angular/compiler@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.4.tgz#326eb0029d9a3541aaca124def9adc51c36f2b41" +"@angular/compiler@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.5.tgz#8721a5910f2bb52f09e2d404cad264f35ede5902" dependencies: tslib "^1.7.1" -"@angular/core@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.4.tgz#bd37ecf54158f97489996c9386bd222f80a32f5c" +"@angular/core@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.5.tgz#54acbcbda11719f883c786a906974abeb132f1a0" dependencies: tslib "^1.7.1" -"@angular/forms@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.4.tgz#4db3790509b6b10f1db8a7c1b7f52187cf64cfd4" +"@angular/forms@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.5.tgz#e9552086232aab2ce1d08ef198b62204ea13c43b" dependencies: tslib "^1.7.1" -"@angular/http@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.4.tgz#667faf616bb624168eafae6ee92e5eba23a9d1f2" +"@angular/http@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.5.tgz#2c735ed842401fc2356419268e288dcf2396e84f" dependencies: tslib "^1.7.1" -"@angular/platform-browser-dynamic@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.4.tgz#c3c9eb854a528556a07054127932e527fa932e14" +"@angular/platform-browser-dynamic@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.5.tgz#774dbdc1d90f775dbf1e319f6ed42b260623b61f" dependencies: tslib "^1.7.1" -"@angular/platform-browser@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.4.tgz#a3898e2e7ba9d84ffa0d47144c6971179c75aee6" +"@angular/platform-browser@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.5.tgz#74eb91c0b758126f26d53ee56c7cf4668bd9cac5" dependencies: tslib "^1.7.1" -"@angular/platform-server@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.4.tgz#73ee41fa1cec8628fcc03174727b27cb0031b22a" +"@angular/platform-server@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.5.tgz#76f23b2c384ed7395dc1793cf85978883ba2cb50" dependencies: parse5 "^3.0.1" tslib "^1.7.1" xhr2 "^0.1.4" -"@angular/router@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.4.tgz#7be391096e843cb3e04f9f05d1d65a88df9bc7cf" +"@angular/router@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.5.tgz#f73130cf487d9a32cc1988afda59665f44a28a89" dependencies: tslib "^1.7.1" -"@angular/tsc-wrapped@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.4.tgz#9841821e55616b826ca160250fe85e15fc74ffc3" +"@angular/tsc-wrapped@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.5.tgz#30a0cbb43a663aa75dca984894be4813778ddc9c" dependencies: tsickle "^0.21.0" @@ -106,9 +106,9 @@ version "4.0.3" resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-4.0.3.tgz#36abacdfa19bfb8506e40de80bae06050a1e15e9" -"@ngtools/webpack@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.2.tgz#3fc4de01786dcc2f50d8cbaaa117311e56799977" +"@ngtools/webpack@1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.4.tgz#5015c47ebd339045dd89a1bef0497f4524d2c8ed" dependencies: enhanced-resolve "^3.1.0" loader-utils "^1.0.2" @@ -192,7 +192,11 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*", "@types/node@8.0.26": +"@types/node@*": + version "8.0.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.34.tgz#55f801fa2ddb2a40dd6dfc15ecfe1dde9c129fe9" + +"@types/node@8.0.26": version "8.0.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.26.tgz#4d58be925306fd22b1141085535a0268b8beb189" @@ -568,7 +572,7 @@ atob@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" -autoprefixer@7.1.5: +autoprefixer@7.1.5, autoprefixer@^7.1.1: version "7.1.5" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.5.tgz#d65d14b83c7cd1dd7bc801daa00557addf5a06b2" dependencies: @@ -590,17 +594,6 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" -autoprefixer@^7.1.1: - version "7.1.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" - dependencies: - browserslist "^2.4.0" - caniuse-lite "^1.0.30000726" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^6.0.11" - postcss-value-parser "^3.2.3" - awesome-typescript-loader@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.2.3.tgz#aa2119b7c808a031e2b28945b031450a8975367f" @@ -793,11 +786,7 @@ blocking-proxy@0.0.5: dependencies: minimist "^1.2.0" -bluebird@^3.3.0, bluebird@^3.4.7: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - -bluebird@^3.5.1: +bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -975,14 +964,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^2.0.0, browserslist@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8" - dependencies: - caniuse-lite "^1.0.30000718" - electron-to-chromium "^1.3.18" - -browserslist@^2.5.0: +browserslist@^2.0.0, browserslist@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.5.1.tgz#68e4bc536bbcc6086d62843a2ffccea8396821c6" dependencies: @@ -1099,11 +1081,7 @@ caniuse-lite@1.0.30000697: version "1.0.30000697" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000697.tgz#125fb00604b63fbb188db96a667ce2922dcd6cdd" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726: - version "1.0.30000740" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" - -caniuse-lite@^1.0.30000744: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000744: version "1.0.30000745" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000745.tgz#20d6fede1157a4935133502946fc7e0e6b880da5" @@ -1126,11 +1104,11 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -cerialize@0.1.16: - version "0.1.16" - resolved "https://registry.yarnpkg.com/cerialize/-/cerialize-0.1.16.tgz#88678bffbd7817a90aa5b58a8c66d6bdca3035be" +cerialize@0.1.18: + version "0.1.18" + resolved "https://registry.yarnpkg.com/cerialize/-/cerialize-0.1.18.tgz#d0f4f1b61cec7e4ed16a3eda0cac2bc99787414d" dependencies: - typescript "^2.1.6" + typescript "^2.5.0" chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" @@ -2042,7 +2020,7 @@ ejs@^2.5.6: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18, electron-to-chromium@^1.3.24: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.24: version "1.3.24" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" @@ -2414,9 +2392,9 @@ express-session@1.15.6: uid-safe "~2.1.5" utils-merge "1.0.1" -express@4.16.1, express@^4.13.3, express@^4.15.2: - version "4.16.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0" +express@4.16.2, express@^4.13.3, express@^4.15.2: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: accepts "~1.3.4" array-flatten "1.1.1" @@ -5129,9 +5107,9 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pem@1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.2.tgz#d2f1744c9ff8144f795f96d0c54a4b2be6f43b0c" +pem@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.3.tgz#b1fb5c8b79da8d18146c27fee79b0d4ddf9905b3" dependencies: md5 "^2.2.1" os-tmpdir "^1.0.1" @@ -5484,12 +5462,12 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" -postcss-loader@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc" +postcss-loader@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.7.tgz#4d2da1489cee0a14f72c0d9440c9ee7eded34345" dependencies: loader-utils "^1.1.0" - postcss "^6.0.2" + postcss "^6.0.0" postcss-load-config "^1.2.0" schema-utils "^0.3.0" @@ -5755,7 +5733,7 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: +postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f" dependencies: @@ -6320,9 +6298,9 @@ requires-port@1.0.x, requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" -resolve-url-loader@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.0.tgz#27c95cc16a4353923fdbdc2dbaf5eef22232c477" +resolve-url-loader@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.1.tgz#5354e87381aae348371e555172c50816708e6c1c" dependencies: adjust-sourcemap-loader "^1.1.0" camelcase "^4.0.0" @@ -7419,7 +7397,7 @@ typescript@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" -typescript@2.5.3, typescript@^2.1.6: +typescript@2.5.3, typescript@^2.5.0: version "2.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" @@ -7827,9 +7805,9 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.5.3" -webpack@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc" +webpack@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.7.1.tgz#6046b5c415ff7df7a0dc54c5b6b86098e8b952da" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" From 2d7bad9e5b6f9bcdad8c525dc3a5feb5bb4173b6 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 20:34:12 -0500 Subject: [PATCH 06/15] metadata service impoved test coverage --- .../core/metadata/metadata.service.spec.ts | 64 +++++++++++++++++-- src/app/core/metadata/metadata.service.ts | 38 ++++++----- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 6e76874604..818a398e72 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -3,7 +3,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { By, Meta } from '@angular/platform-browser'; +import { By, Meta, MetaDefinition } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { Store, StoreModule } from '@ngrx/store'; @@ -23,6 +23,7 @@ import { ResponseCacheService } from '../cache/response-cache.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedItem } from '../cache/models/normalized-item.model'; +import { Item } from '../../core/shared/item.model'; import { MockRouter } from '../../shared/mocks/mock-router'; import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; @@ -72,7 +73,8 @@ describe('MetadataService', () => { CommonModule, StoreModule.forRoot({}), RouterTestingModule.withRoutes([ - { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } } + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full' } ]) ], declarations: [ @@ -104,11 +106,63 @@ describe('MetadataService', () => { spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { observer.next(MockNormalizedItem); })); - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); }); - it('upon navigation should call meta tag setters', () => { + it('items page should set meta tags', fakeAsync(() => { + spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); - }); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.get('citation_title').length).toEqual(1); + expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document'); + })); + + it('items page should set meta tags as published Thesis', fakeAsync(() => { + spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis'))); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.get('citation_dissertation_name').length).toEqual(1); + expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); + expect(tagStore.get('citation_dissertation_institution').length).toEqual(1); + expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); + })); + + it('items page should set meta tags as published Technical Report', fakeAsync(() => { + spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report'))); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.get('citation_technical_report_institution').length).toEqual(1); + expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); + })); + + it('other navigation should clear meta tags', fakeAsync(() => { + router.navigate(['/other']); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.size).toEqual(0); + })); + + const mockType = (mockItem: Item, type: string): Item => { + const typedMockItem = Object.assign({}, mockItem) as Item; + for (const metadatum of typedMockItem.metadata) { + if (metadatum.key === 'dc.type') { + metadatum.value = type; + break; + } + } + return typedMockItem; + } + + const mockPublisher = (mockItem: Item): Item => { + const publishedMockItem = Object.assign({}, mockItem) as Item; + publishedMockItem.metadata.push({ + key: 'dc.publisher', + language: 'en_US', + value: 'Mock Publisher' + }); + return publishedMockItem; + } }); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 9e8c6747f5..a7674ae7f2 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -55,21 +55,25 @@ export class MetadataService { route = this.getCurrentRoute(route); return { params: route.params, data: route.data }; }).subscribe((routeInfo: any) => { - if (routeInfo.params.value.id && routeInfo.data.value.type) { - this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) - .first().subscribe((normalizedObject: CacheableObject) => { - const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; - if (!this.initialized) { - this.initialize(dspaceObject); - } - this.currentObject.next(dspaceObject); - }); - } else { - this.clearMetaTags(); - } + this.processRouteChange(routeInfo); }); } + private processRouteChange(routeInfo: any): void { + if (routeInfo.params.value.id && routeInfo.data.value.type) { + this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) + .first().subscribe((normalizedObject: CacheableObject) => { + const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } else { + this.clearMetaTags(); + } + } + private initialize(dspaceObject: DSpaceObject): void { this.currentObject = new BehaviorSubject(dspaceObject); const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { @@ -246,7 +250,7 @@ export class MetadataService { let isDissertation = false; for (const metadatum of this.currentObject.value.metadata) { if (metadatum.key === 'dc.type') { - isDissertation = metadatum.value === 'Thesis'; + isDissertation = metadatum.value.toLowerCase() === 'thesis'; break; } } @@ -263,7 +267,7 @@ export class MetadataService { let isTechReport = false; for (const metadatum of this.currentObject.value.metadata) { if (metadatum.key === 'dc.type') { - isTechReport = metadatum.value === 'Technical Report'; + isTechReport = metadatum.value.toLowerCase() === 'technical report'; break; } } @@ -344,11 +348,15 @@ export class MetadataService { this.tagStore.set(key, tags); } - private clearMetaTags() { + public clearMetaTags() { this.tagStore.forEach((tags: MetaDefinition[], property: string) => { this.meta.removeTag("property='" + property + "'"); }); this.tagStore.clear(); } + public getTagStore(): Map { + return this.tagStore; + } + } From c37a30ec2afccb2a6f1fce3ff9891cb5d7b5a840 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 20:35:30 -0500 Subject: [PATCH 07/15] upgraded node types --- package.json | 2 +- yarn.lock | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9a5e024941..3959872257 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@types/jasmine": "2.6.0", "@types/memory-cache": "0.0.31", "@types/mime": "2.0.0", - "@types/node": "8.0.26", + "@types/node": "8.0.34", "@types/serve-static": "1.7.32", "@types/source-map": "0.5.1", "@types/webfontloader": "1.6.29", diff --git a/yarn.lock b/yarn.lock index 39e4bf575d..2fcafccd02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,14 +192,10 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*": +"@types/node@*", "@types/node@8.0.34": version "8.0.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.34.tgz#55f801fa2ddb2a40dd6dfc15ecfe1dde9c129fe9" -"@types/node@8.0.26": - version "8.0.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.26.tgz#4d58be925306fd22b1141085535a0268b8beb189" - "@types/node@^6.0.46": version "6.0.88" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66" From 2f9c8468fd3108e6c0e4b01256f287007c7d1cce Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 00:38:26 -0500 Subject: [PATCH 08/15] initial metadata service with full coverage --- .../+home-page/home-page-routing.module.ts | 2 +- .../search-page-routing.module.ts | 2 +- .../normalized-bitstream-format.model.ts | 27 ++++- .../models/normalized-bitstream.model.ts | 7 +- .../cache/models/normalized-object-factory.ts | 8 +- .../core/metadata/metadata.service.spec.ts | 42 ++++--- src/app/core/metadata/metadata.service.ts | 49 ++++++-- src/app/core/shared/bitstream-format.model.ts | 17 +++ src/app/core/shared/bitstream.model.ts | 11 +- src/app/shared/mocks/mock-item.ts | 111 +++++++++++++++++- 10 files changed, 226 insertions(+), 50 deletions(-) create mode 100644 src/app/core/shared/bitstream-format.model.ts diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index e68b633a6d..f70109e3fe 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -6,7 +6,7 @@ import { HomePageComponent } from './home-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full' } + { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'DSpace Angular :: Home' } } ]) ] }) diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index de2d64c6c9..a74a88dcfa 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -6,7 +6,7 @@ import { SearchPageComponent } from './search-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: SearchPageComponent } + { path: '', component: SearchPageComponent, data: { title: 'DSpace Angular :: Search' } } ]) ] }) diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts index 2813794011..bb8b049a1c 100644 --- a/src/app/core/cache/models/normalized-bitstream-format.model.ts +++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts @@ -1,13 +1,30 @@ -import { inheritSerialization } from 'cerialize'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { mapsTo } from '../builders/build-decorators'; + +import { BitstreamFormat } from '../../shared/bitstream-format.model'; import { NormalizedObject } from './normalized-object.model'; +@mapsTo(BitstreamFormat) @inheritSerialization(NormalizedObject) export class NormalizedBitstreamFormat extends NormalizedObject { - // TODO: this class was created as a placeholder when we connected to the live rest api - get uuid(): string { - return this.self; - } + @autoserialize + shortDescription: string; + + @autoserialize + description: string; + + @autoserialize + mimetype: string; + + @autoserialize + supportLevel: number; + + @autoserialize + internal: boolean; + + @autoserialize + extensions: string; } diff --git a/src/app/core/cache/models/normalized-bitstream.model.ts b/src/app/core/cache/models/normalized-bitstream.model.ts index ba5343e252..db8002a874 100644 --- a/src/app/core/cache/models/normalized-bitstream.model.ts +++ b/src/app/core/cache/models/normalized-bitstream.model.ts @@ -21,16 +21,11 @@ export class NormalizedBitstream extends NormalizedDSpaceObject { @autoserialize content: string; - /** - * The mime type of this Bitstream - */ - @autoserialize - mimetype: string; - /** * The format of this Bitstream */ @autoserialize + @relationship(ResourceType.BitstreamFormat, false) format: string; /** diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts index e9f3b1c9e6..3c67b18b3e 100644 --- a/src/app/core/cache/models/normalized-object-factory.ts +++ b/src/app/core/cache/models/normalized-object-factory.ts @@ -15,11 +15,9 @@ export class NormalizedObjectFactory { case ResourceType.Bitstream: { return NormalizedBitstream } - // commented out for now, bitstreamformats aren't used in the UI yet - // and slow things down noticeably - // case ResourceType.BitstreamFormat: { - // return NormalizedBitstreamFormat - // } + case ResourceType.BitstreamFormat: { + return NormalizedBitstreamFormat + } case ResourceType.Bundle: { return NormalizedBundle } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 818a398e72..90501c4308 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angu import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; -import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { Component, DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -58,6 +58,10 @@ describe('MetadataService', () => { let router: Router; let fixture: ComponentFixture; + let tagStore: Map; + + let envConfig: GlobalConfig; + beforeEach(() => { store = new Store(undefined, undefined, undefined); @@ -74,7 +78,7 @@ describe('MetadataService', () => { StoreModule.forRoot({}), RouterTestingModule.withRoutes([ { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: 'other', component: DummyItemComponent, pathMatch: 'full' } + { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } ]) ], declarations: [ @@ -95,11 +99,14 @@ describe('MetadataService', () => { meta = TestBed.get(Meta); metadataService = TestBed.get(MetadataService); + envConfig = TestBed.get(GLOBAL_CONFIG); + router = TestBed.get(Router); location = TestBed.get(Location); fixture = TestBed.createComponent(TestComponent); - fixture.detectChanges(); + + tagStore = metadataService.getTagStore(); }); beforeEach(() => { @@ -112,40 +119,45 @@ describe('MetadataService', () => { spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); - const tagStore: Map = metadataService.getTagStore(); - expect(tagStore.get('citation_title').length).toEqual(1); expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document'); + expect(tagStore.get('citation_author')[0].content).toEqual('Doe, Jane'); + expect(tagStore.get('citation_date')[0].content).toEqual('1650-06-26T19:58:25Z'); + expect(tagStore.get('citation_issn')[0].content).toEqual('123456789'); + expect(tagStore.get('citation_language')[0].content).toEqual('en'); + expect(tagStore.get('citation_keywords')[0].content).toEqual('keyword1; keyword2; keyword3'); })); it('items page should set meta tags as published Thesis', fakeAsync(() => { spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis'))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); - const tagStore: Map = metadataService.getTagStore(); - expect(tagStore.get('citation_dissertation_name').length).toEqual(1); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); - expect(tagStore.get('citation_dissertation_institution').length).toEqual(1); expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); + expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([envConfig.ui.baseUrl, router.url].join('')); + expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report'))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); - const tagStore: Map = metadataService.getTagStore(); - expect(tagStore.get('citation_technical_report_institution').length).toEqual(1); expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); })); - it('other navigation should clear meta tags', fakeAsync(() => { + it('other navigation should title and description', fakeAsync(() => { + spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + expect(tagStore.size).toBeGreaterThan(0) router.navigate(['/other']); tick(); - const tagStore: Map = metadataService.getTagStore(); - expect(tagStore.size).toEqual(0); + expect(tagStore.size).toEqual(2); + expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); + expect(tagStore.get('description')[0].content).toEqual('This is a dummy component for testing!'); })); const mockType = (mockItem: Item, type: string): Item => { - const typedMockItem = Object.assign({}, mockItem) as Item; + const typedMockItem = Object.assign(new Item(), mockItem) as Item; for (const metadatum of typedMockItem.metadata) { if (metadatum.key === 'dc.type') { metadatum.value = type; @@ -156,7 +168,7 @@ describe('MetadataService', () => { } const mockPublisher = (mockItem: Item): Item => { - const publishedMockItem = Object.assign({}, mockItem) as Item; + const publishedMockItem = Object.assign(new Item(), mockItem) as Item; publishedMockItem.metadata.push({ key: 'dc.publisher', language: 'en_US', diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index a7674ae7f2..cfc4744161 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -40,15 +40,17 @@ export class MetadataService { private meta: Meta, @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig ) { + // TODO: this.meta.addTags([ - { property: 'og:title', content: 'DSpace Angular Universal' } + { property: 'og:title', content: 'DSpace Angular Universal' }, + { property: 'og:description', content: 'The modern front-end for DSpace 7.' } ]); this.initialized = false; this.tagStore = new Map(); } public listenForRouteChange(): void { - const subscription = this.router.events + this.router.events .filter((event) => event instanceof NavigationEnd) .map(() => this.router.routerState.root) .map((route: ActivatedRoute) => { @@ -60,6 +62,7 @@ export class MetadataService { } private processRouteChange(routeInfo: any): void { + this.clearMetaTags(); if (routeInfo.params.value.id && routeInfo.data.value.type) { this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) .first().subscribe((normalizedObject: CacheableObject) => { @@ -70,13 +73,18 @@ export class MetadataService { this.currentObject.next(dspaceObject); }); } else { - this.clearMetaTags(); + if (routeInfo.data.value.title) { + this.addMetaTag('title', routeInfo.data.value.title); + } + if (routeInfo.data.value.description) { + this.addMetaTag('description', routeInfo.data.value.description); + } } } private initialize(dspaceObject: DSpaceObject): void { this.currentObject = new BehaviorSubject(dspaceObject); - const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { + this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { this.setMetaTags(); }); this.initialized = true; @@ -91,7 +99,8 @@ export class MetadataService { private setMetaTags(): void { - this.clearMetaTags(); + this.setTitleTag(); + this.setDescriptionTag(); this.setCitationTitleTag(); this.setCitationAuthorTags(); @@ -131,6 +140,23 @@ export class MetadataService { } + /** + * Add to the + */ + private setTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('title', value); + } + + /** + * Add to the + */ + private setDescriptionTag(): void { + // TODO: truncate abstract + const value = this.getMetaTagValue('dc.description.abstract'); + this.addMetaTag('desciption', value); + } + /** * Add to the */ @@ -175,7 +201,7 @@ export class MetadataService { * Add to the */ private setCitationLanguageTag(): void { - const value = this.getMetaTagValue('dc.language.iso'); + const value = this.getFirstMetaTagValue(['dc.language', 'dc.language.iso']); this.addMetaTag('citation_language', value); } @@ -229,12 +255,13 @@ export class MetadataService { const item = this.currentObject.value as Item; // NOTE: Observable resolves many times with same data // taking only two, fist one is empty array - const subscription = item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => { + item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => { for (const bitstream of bitstreams) { - if (bitstream.mimetype === 'application/pdf') { - this.addMetaTag('citation_abstract_html_url', bitstream.content); - break; - } + bitstream.format.payload.take(1).subscribe((format) => { + if (format.mimetype === 'application/pdf') { + this.addMetaTag('citation_pdf_url', bitstream.content); + } + }); } }); } diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts new file mode 100644 index 0000000000..c0f6be29c9 --- /dev/null +++ b/src/app/core/shared/bitstream-format.model.ts @@ -0,0 +1,17 @@ +import { DSpaceObject } from './dspace-object.model'; + +export class BitstreamFormat extends DSpaceObject { + + shortDescription: string; + + description: string; + + mimetype: string; + + supportLevel: number; + + internal: boolean; + + extensions: string; + +} diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index 5e4ee929d4..0b77a7b032 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -1,6 +1,7 @@ import { DSpaceObject } from './dspace-object.model'; import { RemoteData } from '../data/remote-data'; import { Item } from './item.model'; +import { BitstreamFormat } from './bitstream-format.model'; export class Bitstream extends DSpaceObject { @@ -9,11 +10,6 @@ export class Bitstream extends DSpaceObject { */ sizeBytes: number; - /** - * The mime type of this Bitstream - */ - mimetype: string; - /** * The description of this Bitstream */ @@ -24,6 +20,11 @@ export class Bitstream extends DSpaceObject { */ bundleName: string; + /** + * An array of Bitstream Format of this Bitstream + */ + format: RemoteData; + /** * An array of Items that are direct parents of this Bitstream */ diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts index 43ad178485..0331491aa0 100644 --- a/src/app/shared/mocks/mock-item.ts +++ b/src/app/shared/mocks/mock-item.ts @@ -2,6 +2,7 @@ import { Observable } from 'rxjs/Observable'; import { Item } from '../../core/shared/item.model'; +/* tslint:disable:no-shadowed-variable */ export const MockItem: Item = Object.assign(new Item(), { handle: '10673/6', lastModified: '2017-04-24T19:44:08.178+0000', @@ -33,7 +34,114 @@ export const MockItem: Item = Object.assign(new Item(), { observer.next({}); }), payload: Observable.create((observer) => { - observer.next([]); + observer.next([ + { + sizeBytes: 10201, + content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', + format: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next({ + shortDescription: 'Microsoft Word XML', + description: 'Microsoft Word XML', + mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + supportLevel: 0, + internal: false, + extensions: null, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10' + }); + }) + }, + bundleName: 'ORIGINAL', + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713', + id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', + uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', + type: 'bitstream', + name: 'test_word.docx', + metadata: [ + { + key: 'dc.title', + language: null, + value: 'test_word.docx' + } + ] + }, + { + sizeBytes: 31302, + content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content', + format: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next({ + shortDescription: 'Adobe PDF', + description: 'Adobe Portable Document Format', + mimetype: 'application/pdf', + supportLevel: 0, + internal: false, + extensions: null, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4' + }); + }) + }, + bundleName: 'ORIGINAL', + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28', + id: '99b00f3c-1cc6-4689-8158-91965bee6b28', + uuid: '99b00f3c-1cc6-4689-8158-91965bee6b28', + type: 'bitstream', + name: 'test_pdf.pdf', + metadata: [ + { + key: 'dc.title', + language: null, + value: 'test_pdf.pdf' + } + ] + } + ]); }) }, self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', @@ -162,3 +270,4 @@ export const MockItem: Item = Object.assign(new Item(), { }) } }) +/* tslint:enable:no-shadowed-variable */ From 366f350970b359194892d4553d1af1d561631304 Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 00:43:14 -0500 Subject: [PATCH 09/15] removed unused import, added todo comment --- src/app/core/metadata/metadata.service.spec.ts | 2 +- src/app/core/metadata/metadata.service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 90501c4308..803343c12c 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angu import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; -import { Component, DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition } from '@angular/platform-browser'; import { Router } from '@angular/router'; diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index cfc4744161..60c2004071 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -40,7 +40,8 @@ export class MetadataService { private meta: Meta, @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig ) { - // TODO: + // TODO: determine what open graph meta tags are needed and whether + // the differ per route. potentially add image based on DSpaceObject this.meta.addTags([ { property: 'og:title', content: 'DSpace Angular Universal' }, { property: 'og:description', content: 'The modern front-end for DSpace 7.' } From 617f3ad0294a06e88697c302200e6db1c2a585bc Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 08:32:05 -0500 Subject: [PATCH 10/15] updating title element on route change with i18n translation --- e2e/app.e2e-spec.ts | 4 ++-- resources/i18n/en.json | 4 ++++ .../+home-page/home-page-routing.module.ts | 2 +- .../search-page-routing.module.ts | 2 +- .../core/metadata/metadata.service.spec.ts | 23 +++++++++++++++---- src/app/core/metadata/metadata.service.ts | 21 +++++++++++++---- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts index 79b9f251dd..f5ac9094d0 100644 --- a/e2e/app.e2e-spec.ts +++ b/e2e/app.e2e-spec.ts @@ -7,9 +7,9 @@ describe('protractor App', () => { page = new ProtractorPage(); }); - it('should display title "DSpace"', () => { + it('should display translated title "DSpace Angular :: Home"', () => { page.navigateTo(); - expect(page.getPageTitleText()).toEqual('DSpace'); + expect(page.getPageTitleText()).toEqual('DSpace Angular :: Home'); }); it('should display header "Welcome to DSpace"', () => { diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 3388190295..2239d605cc 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -65,12 +65,16 @@ } }, "home": { + "title": "DSpace Angular :: Home", + "description": "", "top-level-communities": { "head": "Communities in DSpace", "help": "Select a community to browse its collections." } }, "search": { + "title": "DSpace Angular :: Search", + "description": "", "form": { "search": "Search", "search_dspace": "Search DSpace" diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index f70109e3fe..d7dcc18f49 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -6,7 +6,7 @@ import { HomePageComponent } from './home-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'DSpace Angular :: Home' } } + { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } } ]) ] }) diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index a74a88dcfa..65cca99a34 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -6,7 +6,7 @@ import { SearchPageComponent } from './search-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: SearchPageComponent, data: { title: 'DSpace Angular :: Search' } } + { path: '', component: SearchPageComponent, data: { title: 'search.title' } } ]) ] }) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 803343c12c..6f740b9f18 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -3,9 +3,11 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { By, Meta, MetaDefinition } from '@angular/platform-browser'; +import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; + import { Store, StoreModule } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; @@ -22,12 +24,13 @@ import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; import { Item } from '../../core/shared/item.model'; +import { NormalizedItem } from '../cache/models/normalized-item.model'; -import { MockRouter } from '../../shared/mocks/mock-router'; -import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; import { MockItem } from '../../shared/mocks/mock-item'; +import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; +import { MockRouter } from '../../shared/mocks/mock-router'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -47,6 +50,8 @@ describe('MetadataService', () => { let meta: Meta; + let title: Title; + let store: Store; let objectCacheService: ObjectCacheService; @@ -76,6 +81,12 @@ describe('MetadataService', () => { imports: [ CommonModule, StoreModule.forRoot({}), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), RouterTestingModule.withRoutes([ { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } @@ -92,11 +103,13 @@ describe('MetadataService', () => { { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, Meta, + Title, MetadataService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); meta = TestBed.get(Meta); + title = TestBed.get(Title); metadataService = TestBed.get(MetadataService); envConfig = TestBed.get(GLOBAL_CONFIG); @@ -119,6 +132,7 @@ describe('MetadataService', () => { spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); + expect(title.getTitle()).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_author')[0].content).toEqual('Doe, Jane'); expect(tagStore.get('citation_date')[0].content).toEqual('1650-06-26T19:58:25Z'); @@ -152,6 +166,7 @@ describe('MetadataService', () => { router.navigate(['/other']); tick(); expect(tagStore.size).toEqual(2); + expect(title.getTitle()).toEqual('Dummy Title'); expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); expect(tagStore.get('description')[0].content).toEqual('This is a dummy component for testing!'); })); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 60c2004071..4090d2b484 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -9,7 +9,10 @@ import { Params, Router } from '@angular/router'; -import { Meta, MetaDefinition } from '@angular/platform-browser'; + +import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; @@ -37,7 +40,9 @@ export class MetadataService { private router: Router, private objectCacheService: ObjectCacheService, private remoteDataBuildService: RemoteDataBuildService, + private translate: TranslateService, private meta: Meta, + private title: Title, @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig ) { // TODO: determine what open graph meta tags are needed and whether @@ -63,7 +68,6 @@ export class MetadataService { } private processRouteChange(routeInfo: any): void { - this.clearMetaTags(); if (routeInfo.params.value.id && routeInfo.data.value.type) { this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) .first().subscribe((normalizedObject: CacheableObject) => { @@ -74,11 +78,17 @@ export class MetadataService { this.currentObject.next(dspaceObject); }); } else { + this.clearMetaTags(); if (routeInfo.data.value.title) { - this.addMetaTag('title', routeInfo.data.value.title); + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); } if (routeInfo.data.value.description) { - this.addMetaTag('description', routeInfo.data.value.description); + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); } } } @@ -100,6 +110,8 @@ export class MetadataService { private setMetaTags(): void { + this.clearMetaTags(); + this.setTitleTag(); this.setDescriptionTag(); @@ -147,6 +159,7 @@ export class MetadataService { private setTitleTag(): void { const value = this.getMetaTagValue('dc.title'); this.addMetaTag('title', value); + this.title.setTitle(value); } /** From 6f42fa5af5bc19c96a4e27d4af4b546668c9dcd8 Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 13:09:00 -0500 Subject: [PATCH 11/15] moved meta service into applicable page components --- .../collection-page-routing.module.ts | 3 +- .../collection-page.component.ts | 18 ++- .../community-page-routing.module.ts | 3 +- .../community-page.component.ts | 11 +- .../full/full-item-page.component.ts | 10 +- .../+item-page/item-page-routing.module.ts | 5 +- .../+item-page/simple/item-page.component.ts | 9 +- .../core/metadata/metadata.service.spec.ts | 69 ++++++++--- src/app/core/metadata/metadata.service.ts | 47 ++++---- src/app/shared/mocks/mock-normalized-item.ts | 114 ------------------ 10 files changed, 108 insertions(+), 181 deletions(-) delete mode 100644 src/app/shared/mocks/mock-normalized-item.ts diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index fc7f58b6fb..c886aa655c 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; -import { NormalizedCollection } from '../core/cache/models/normalized-collection.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CollectionPageComponent, pathMatch: 'full', data: { type: NormalizedCollection } } + { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 65bf708727..30d9c17fe3 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -7,6 +7,8 @@ import { } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; +import { PageInfo } from '../core/shared/page-info.model'; +import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { Collection } from '../core/shared/collection.model'; @@ -18,8 +20,8 @@ import { Item } from '../core/shared/item.model'; import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { hasValue, isNotEmpty, isUndefined } from '../shared/empty.util'; -import { PageInfo } from '../core/shared/page-info.model'; -import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from '../core/metadata/metadata.service'; import { fadeIn, fadeInOut } from '../shared/animations/fade'; @@ -41,9 +43,12 @@ export class CollectionPageComponent implements OnInit, OnDestroy { private subs: Subscription[] = []; private collectionId: string; - constructor(private collectionDataService: CollectionDataService, - private itemDataService: ItemDataService, - private route: ActivatedRoute) { + constructor( + private collectionDataService: CollectionDataService, + private itemDataService: ItemDataService, + private metadata: MetadataService, + private route: ActivatedRoute + ) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.pageSizeOptions = [4]; @@ -57,12 +62,13 @@ export class CollectionPageComponent implements OnInit, OnDestroy { Observable.combineLatest( this.route.params, this.route.queryParams, - (params, queryParams,) => { + (params, queryParams, ) => { return Object.assign({}, params, queryParams); }) .subscribe((params) => { this.collectionId = params.id; this.collectionData = this.collectionDataService.findById(this.collectionId); + this.metadata.processRemoteData(this.collectionData); this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo)); const page = +params.page || this.paginationConfig.currentPage; diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e761717808..6fd5cc8cb5 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; -import { NormalizedCommunity } from '../core/cache/models/normalized-community.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CommunityPageComponent, pathMatch: 'full', data: { type: NormalizedCommunity } } + { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 1295e14521..0cd94658be 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -9,6 +9,8 @@ import { RemoteData } from '../core/data/remote-data'; import { CommunityDataService } from '../core/data/community-data.service'; import { hasValue } from '../shared/empty.util'; +import { MetadataService } from '../core/metadata/metadata.service'; + import { fadeInOut } from '../shared/animations/fade'; @Component({ @@ -24,6 +26,7 @@ export class CommunityPageComponent implements OnInit, OnDestroy { constructor( private communityDataService: CommunityDataService, + private metadata: MetadataService, private route: ActivatedRoute ) { @@ -32,15 +35,13 @@ export class CommunityPageComponent implements OnInit, OnDestroy { ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.communityData = this.communityDataService.findById(params.id); - this.subs.push(this.communityData.payload - .subscribe((community) => this.logoData = community.logo)); + this.metadata.processRemoteData(this.communityData); + this.subs.push(this.communityData.payload.subscribe((community) => this.logoData = community.logo)); }); } ngOnDestroy(): void { - this.subs - .filter((sub) => hasValue(sub)) - .forEach((sub) => sub.unsubscribe()); + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+item-page/full/full-item-page.component.ts b/src/app/+item-page/full/full-item-page.component.ts index 337c598021..270cf1fcae 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,15 +1,17 @@ import { Component, OnInit } from '@angular/core'; -import { animate, state, transition, trigger, style, keyframes } from '@angular/animations'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { ItemPageComponent } from '../simple/item-page.component'; import { Metadatum } from '../../core/shared/metadatum.model'; import { ItemDataService } from '../../core/data/item-data.service'; -import { ActivatedRoute } from '@angular/router'; + import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -30,8 +32,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit { metadata: Observable; - constructor(route: ActivatedRoute, items: ItemDataService) { - super(route, items); + constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) { + super(route, items, metadataService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index e315b18a9c..9dca4d0f6e 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -3,13 +3,12 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; -import { NormalizedItem } from '../core/cache/models/normalized-item.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: ItemPageComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: ':id/full', component: FullItemPageComponent, data: { type: NormalizedItem } } + { path: ':id', component: ItemPageComponent, pathMatch: 'full' }, + { path: ':id/full', component: FullItemPageComponent } ]) ] }) diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 8f1bd18b4e..ce9283e144 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -8,6 +8,8 @@ import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Bitstream } from '../../core/shared/bitstream.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -31,7 +33,11 @@ export class ItemPageComponent implements OnInit { thumbnail: Observable; - constructor(private route: ActivatedRoute, private items: ItemDataService) { + constructor( + private route: ActivatedRoute, + private items: ItemDataService, + private metadataService: MetadataService + ) { } @@ -44,6 +50,7 @@ export class ItemPageComponent implements OnInit { initialize(params) { this.id = +params.id; this.item = this.items.findById(params.id); + this.metadataService.processRemoteData(this.item); this.thumbnail = this.item.payload.flatMap((i) => i.getThumbnail()); } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 6f740b9f18..1258751f58 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -4,7 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; @@ -19,17 +19,16 @@ import { CoreState } from '../core.reducers'; import { GlobalConfig } from '../../../config/global-config.interface'; import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; +import { ItemDataService } from '../data/item-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; import { MockItem } from '../../shared/mocks/mock-item'; -import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; -import { MockRouter } from '../../shared/mocks/mock-router'; import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; /* tslint:disable:max-classes-per-file */ @@ -42,7 +41,13 @@ class TestComponent { } } -@Component({ template: '' }) class DummyItemComponent { } +@Component({ template: '' }) class DummyItemComponent { + constructor(private route: ActivatedRoute, private items: ItemDataService, private metadata: MetadataService) { + this.route.params.subscribe((params) => { + this.metadata.processRemoteData(this.items.findById(params.id)); + }); + } +} /* tslint:enable:max-classes-per-file */ describe('MetadataService', () => { @@ -58,6 +63,7 @@ describe('MetadataService', () => { let responseCacheService: ResponseCacheService; let requestService: RequestService; let remoteDataBuildService: RemoteDataBuildService; + let itemDataService: ItemDataService; let location: Location; let router: Router; @@ -88,8 +94,8 @@ describe('MetadataService', () => { } }), RouterTestingModule.withRoutes([ - { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full' }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy item component for testing!' } } ]) ], declarations: [ @@ -104,12 +110,14 @@ describe('MetadataService', () => { { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, Meta, Title, + ItemDataService, MetadataService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); meta = TestBed.get(Meta); title = TestBed.get(Title); + itemDataService = TestBed.get(ItemDataService); metadataService = TestBed.get(MetadataService); envConfig = TestBed.get(GLOBAL_CONFIG); @@ -122,14 +130,8 @@ describe('MetadataService', () => { tagStore = metadataService.getTagStore(); }); - beforeEach(() => { - spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { - observer.next(MockNormalizedItem); - })); - }); - it('items page should set meta tags', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(title.getTitle()).toEqual('Test PowerPoint Document'); @@ -142,7 +144,7 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Thesis', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Thesis')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); @@ -152,14 +154,14 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Technical Report')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); })); it('other navigation should title and description', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.size).toBeGreaterThan(0) @@ -168,9 +170,38 @@ describe('MetadataService', () => { expect(tagStore.size).toEqual(2); expect(title.getTitle()).toEqual('Dummy Title'); expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); - expect(tagStore.get('description')[0].content).toEqual('This is a dummy component for testing!'); + expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!'); })); + const mockRemoteData = (mockItem: Item): RemoteData => { + return new RemoteData( + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(true); + }), + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(200); + }), + Observable.create((observer) => { + observer.next({}); + }), + Observable.create((observer) => { + observer.next(MockItem); + }) + ); + } + const mockType = (mockItem: Item, type: string): Item => { const typedMockItem = Object.assign(new Item(), mockItem) as Item; for (const metadatum of typedMockItem.metadata) { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 4090d2b484..32b002e721 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -17,13 +17,12 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../data/remote-data'; import { Bitstream } from '../shared/bitstream.model'; import { CacheableObject } from '../cache/object-cache.reducer'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; import { Metadatum } from '../shared/metadatum.model'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @@ -38,8 +37,6 @@ export class MetadataService { constructor( private router: Router, - private objectCacheService: ObjectCacheService, - private remoteDataBuildService: RemoteDataBuildService, private translate: TranslateService, private meta: Meta, private title: Title, @@ -67,29 +64,29 @@ export class MetadataService { }); } + public processRemoteData(remoteData: RemoteData): void { + remoteData.payload.take(1).subscribe((dspaceObject: DSpaceObject) => { + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } + private processRouteChange(routeInfo: any): void { - if (routeInfo.params.value.id && routeInfo.data.value.type) { - this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) - .first().subscribe((normalizedObject: CacheableObject) => { - const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; - if (!this.initialized) { - this.initialize(dspaceObject); - } - this.currentObject.next(dspaceObject); - }); - } else { + if (routeInfo.params.value.id === undefined) { this.clearMetaTags(); - if (routeInfo.data.value.title) { - this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { - this.addMetaTag('title', translatedTitle); - this.title.setTitle(translatedTitle); - }); - } - if (routeInfo.data.value.description) { - this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { - this.addMetaTag('description', translatedDescription); - }); - } + } + if (routeInfo.data.value.title) { + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); + } + if (routeInfo.data.value.description) { + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); } } diff --git a/src/app/shared/mocks/mock-normalized-item.ts b/src/app/shared/mocks/mock-normalized-item.ts deleted file mode 100644 index d8716b4bf2..0000000000 --- a/src/app/shared/mocks/mock-normalized-item.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NormalizedItem } from '../../core/cache/models/normalized-item.model'; - -export const MockNormalizedItem: NormalizedItem = Object.assign(new NormalizedItem(), { - handle: '10673/6', - lastModified: new Date('2017-04-24T19:44:08.178+0000'), - isArchived: true, - isDiscoverable: true, - isWithdrawn: false, - bitstreams: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/a7cd7d97-4e40-41db-80a8-fac908b63bb8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/9ff3df0d-1709-472f-8c00-d3e8db2153c8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/d660a4b8-e7cc-45cd-b026-35f98c5bd3ba' - ], - self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', - id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - type: 'item', - name: 'Test PowerPoint Document', - metadata: [ - { - key: 'dc.creator', - language: 'en_US', - value: 'Doe, Jane L' - }, - { - key: 'dc.date.accessioned', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.available', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.issued', - language: null, - value: '1650-06-26' - }, - { - key: 'dc.identifier.issn', - language: 'en_US', - value: '123456789' - }, - { - key: 'dc.identifier.uri', - language: null, - value: 'http://dspace7.4science.it/xmlui/handle/10673/6' - }, - { - key: 'dc.description.abstract', - language: 'en_US', - value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' - }, - { - key: 'dc.language', - language: 'en_US', - value: 'en' - }, - { - key: 'dc.rights', - language: 'en_US', - value: '© Jane Doe' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword1' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword2' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword3' - }, - { - key: 'dc.title', - language: 'en_US', - value: 'Test PowerPoint Document' - }, - { - key: 'dc.type', - language: 'en_US', - value: 'text' - } - ], - owningCollection: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb' - ] -}) From eab2dd86e2efe0f4569f6bd9b30902e63dfe0486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Mon, 9 Oct 2017 12:05:15 +0200 Subject: [PATCH 12/15] Implement component to switch between view modes For now it allows to switch between list and grid views. Fixes https://github.com/DSpace/dspace-angular/issues/171 --- resources/i18n/en.json | 4 ++++ src/app/+search-page/search-options.model.ts | 6 ++++++ .../+search-page/search-page.component.html | 1 + src/app/+search-page/search-page.component.ts | 1 + .../search-service/search.service.ts | 15 +++++++++++++- src/app/shared/shared.module.ts | 4 +++- .../view-mode-switch.component.html | 18 +++++++++++++++++ .../view-mode-switch.component.scss | 1 + .../view-mode-switch.component.ts | 20 +++++++++++++++++++ 9 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/app/shared/view-mode-switch/view-mode-switch.component.html create mode 100644 src/app/shared/view-mode-switch/view-mode-switch.component.scss create mode 100644 src/app/shared/view-mode-switch/view-mode-switch.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 2239d605cc..812486d64c 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -81,6 +81,10 @@ }, "results": { "title": "Search Results" + }, + "view-switch": { + "show-list": "Show as list", + "show-grid": "Show as grid" } }, "loading": { diff --git a/src/app/+search-page/search-options.model.ts b/src/app/+search-page/search-options.model.ts index fd4a5accf6..7f93c3ace1 100644 --- a/src/app/+search-page/search-options.model.ts +++ b/src/app/+search-page/search-options.model.ts @@ -1,7 +1,13 @@ import { SortOptions } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +export enum ViewMode { + List = 'list', + Grid = 'grid' +} + export class SearchOptions { pagination?: PaginationComponentOptions; sort?: SortOptions; + view?: ViewMode = ViewMode.List; } diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index d5e3a831eb..c651c8a329 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -5,5 +5,6 @@ [currentParams]="currentParams" [scopes]="scopeList?.payload"> + diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 639e966a9b..723753fb7a 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -6,6 +6,7 @@ import { SearchResult } from './search-result.model'; import { DSpaceObject } from '../core/shared/dspace-object.model'; import { SortOptions } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { ViewModeSwitchComponent } from '../shared/view-mode-switch/view-mode-switch.component'; import { SearchOptions } from './search-options.model'; import { CommunityDataService } from '../core/data/community-data.service'; import { isNotEmpty } from '../shared/empty.util'; diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index e2804960ef..5f3ef7ce22 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -13,6 +13,8 @@ import { ItemSearchResult } from '../../object-list/search-result-list-element/i import { SearchFilterConfig } from './search-filter-config.model'; import { FilterType } from './filter-type.model'; import { FacetValue } from './facet-value.model'; +import { ViewMode } from '../../+search-page/search-options.model'; +import { Router, NavigationExtras } from '@angular/router'; function shuffle(array: any[]) { let i = 0; @@ -76,7 +78,9 @@ export class SearchService { }) ]; - constructor(private itemDataService: ItemDataService) { + constructor( + private itemDataService: ItemDataService, + private router: Router) { } @@ -192,4 +196,13 @@ export class SearchService { Observable.of(values) ); } + + setViewMode(viewMode: ViewMode) { + const navigationExtras: NavigationExtras = { + queryParams: {view: viewMode}, + queryParamsHandling: 'merge' + }; + + this.router.navigate(['/search'], navigationExtras); + } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 99634543eb..5b6146b7a4 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -29,6 +29,7 @@ import { ThumbnailComponent } from '../thumbnail/thumbnail.component'; import { SearchResultListElementComponent } from '../object-list/search-result-list-element/search-result-list-element.component'; import { SearchFormComponent } from './search-form/search-form.component'; import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component'; +import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -61,7 +62,8 @@ const COMPONENTS = [ PaginationComponent, SearchFormComponent, ThumbnailComponent, - WrapperListElementComponent + WrapperListElementComponent, + ViewModeSwitchComponent ]; const ENTRY_COMPONENTS = [ diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.html b/src/app/shared/view-mode-switch/view-mode-switch.component.html new file mode 100644 index 0000000000..d399ff122c --- /dev/null +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.html @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.scss b/src/app/shared/view-mode-switch/view-mode-switch.component.scss new file mode 100644 index 0000000000..ad84b72f8c --- /dev/null +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.scss @@ -0,0 +1 @@ +@import '../../../styles/variables.scss'; \ No newline at end of file diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.ts b/src/app/shared/view-mode-switch/view-mode-switch.component.ts new file mode 100644 index 0000000000..ab1585bb58 --- /dev/null +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../+search-page/search-options.model'; +import { SearchService } from './../../+search-page/search-service/search.service'; + +/** + * Component to switch between list and grid views. + */ +@Component({ + selector: 'ds-view-mode-switch', + styleUrls: ['./view-mode-switch.component.scss'], + templateUrl: './view-mode-switch.component.html' +}) +export class ViewModeSwitchComponent { + constructor(private searchService: SearchService) { + } + + switchViewTo(viewMode: ViewMode) { + this.searchService.setViewMode(viewMode); + } +} From d6498d991fc037f41094551ad325211673a8d701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Mon, 16 Oct 2017 12:56:06 +0200 Subject: [PATCH 13/15] Show the button of the default view as active on first load Using just routerLinkActive doesn't work on first load, the query parameter might not be defined. Adding 'active' to the class attribute doesn't work either, it doesn't get removed after switching to the other view. --- .../search-service/search.service.ts | 13 +++++++++++- .../view-mode-switch.component.html | 2 ++ .../view-mode-switch.component.ts | 21 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 5f3ef7ce22..a06cbd90b8 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -14,7 +14,7 @@ import { SearchFilterConfig } from './search-filter-config.model'; import { FilterType } from './filter-type.model'; import { FacetValue } from './facet-value.model'; import { ViewMode } from '../../+search-page/search-options.model'; -import { Router, NavigationExtras } from '@angular/router'; +import { Router, NavigationExtras, ActivatedRoute } from '@angular/router'; function shuffle(array: any[]) { let i = 0; @@ -80,6 +80,7 @@ export class SearchService { constructor( private itemDataService: ItemDataService, + private route: ActivatedRoute, private router: Router) { } @@ -197,6 +198,16 @@ export class SearchService { ); } + getViewMode(): Observable { + return this.route.queryParams.map((params) => { + if (isNotEmpty(params.view) && hasValue(params.view)) { + return params.view; + } else { + return ViewMode.List; + } + }); + } + setViewMode(viewMode: ViewMode) { const navigationExtras: NavigationExtras = { queryParams: {view: viewMode}, diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.html b/src/app/shared/view-mode-switch/view-mode-switch.component.html index d399ff122c..fb5e51a095 100644 --- a/src/app/shared/view-mode-switch/view-mode-switch.component.html +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.html @@ -4,6 +4,7 @@ queryParamsHandling="merge" (click)="switchViewTo(viewModeEnum.List)" routerLinkActive="active" + [class.active]="currentMode === viewModeEnum.List" class="btn btn-secondary"> @@ -12,6 +13,7 @@ queryParamsHandling="merge" (click)="switchViewTo(viewModeEnum.Grid)" routerLinkActive="active" + [class.active]="currentMode !== viewModeEnum.List" class="btn btn-secondary"> diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.ts b/src/app/shared/view-mode-switch/view-mode-switch.component.ts index ab1585bb58..f6e04816fb 100644 --- a/src/app/shared/view-mode-switch/view-mode-switch.component.ts +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { ViewMode } from '../../+search-page/search-options.model'; import { SearchService } from './../../+search-page/search-service/search.service'; @@ -10,11 +11,27 @@ import { SearchService } from './../../+search-page/search-service/search.servic styleUrls: ['./view-mode-switch.component.scss'], templateUrl: './view-mode-switch.component.html' }) -export class ViewModeSwitchComponent { +export class ViewModeSwitchComponent implements OnInit, OnDestroy { + currentMode: ViewMode = ViewMode.List; + viewModeEnum = ViewMode; + private sub: Subscription; + constructor(private searchService: SearchService) { } + ngOnInit(): void { + this.sub = this.searchService.getViewMode().subscribe((viewMode: ViewMode) => { + this.currentMode = viewMode; + }); + } + switchViewTo(viewMode: ViewMode) { this.searchService.setViewMode(viewMode); } + + ngOnDestroy() { + if (this.sub !== undefined) { + this.sub.unsubscribe(); + } + } } From 4e285e5a2c079cc1377b5b2ac9554b17cfa13236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Wed, 18 Oct 2017 10:11:47 +0200 Subject: [PATCH 14/15] Add tests for SearchService --- .../search-service/search.service.spec.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/app/+search-page/search-service/search.service.spec.ts diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts new file mode 100644 index 0000000000..489ac76763 --- /dev/null +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -0,0 +1,56 @@ +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +import { SearchService } from './search.service'; +import { ItemDataService } from './../../core/data/item-data.service'; +import { ViewMode } from '../../+search-page/search-options.model'; + +@Component({ template: '' }) +class DummyComponent { } + +describe('SearchService', () => { + let searchService: SearchService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + RouterTestingModule.withRoutes([ + { path: 'search', component: DummyComponent, pathMatch: 'full' }, + ]) + ], + declarations: [ + DummyComponent + ], + providers: [ + { provide: ItemDataService, useValue: {} }, + SearchService + ], + }); + searchService = TestBed.get(SearchService); + }); + + it('should return list view mode by default', () => { + searchService.getViewMode().subscribe((viewMode) => { + expect(viewMode).toBe(ViewMode.List); + }); + }); + + it('should return the view mode set through setViewMode', fakeAsync(() => { + searchService.setViewMode(ViewMode.Grid) + tick(); + let viewMode = ViewMode.List; + searchService.getViewMode().subscribe((mode) => viewMode = mode); + expect(viewMode).toBe(ViewMode.Grid); + + searchService.setViewMode(ViewMode.List) + tick(); + searchService.getViewMode().subscribe((mode) => viewMode = mode); + expect(viewMode).toBe(ViewMode.List); + })); + +}); From cbac9ab1de5f3d469b8b4730fd7e91bd01107f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Wed, 18 Oct 2017 17:12:41 +0200 Subject: [PATCH 15/15] Add tests for ViewModeSwitchComponent --- .../view-mode-switch.component.spec.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts b/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts new file mode 100644 index 0000000000..a8486d011d --- /dev/null +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts @@ -0,0 +1,77 @@ +import { DebugElement } from '@angular/core'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { Component } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { SearchService } from '../../+search-page/search-service/search.service'; +import { ItemDataService } from './../../core/data/item-data.service'; +import { ViewModeSwitchComponent } from './view-mode-switch.component'; +import { ViewMode } from '../../+search-page/search-options.model'; + +@Component({ template: '' }) +class DummyComponent { } + +describe('ViewModeSwitchComponent', () => { + let comp: ViewModeSwitchComponent; + let fixture: ComponentFixture; + let searchService: SearchService; + let listButton: HTMLElement; + let gridButton: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), + RouterTestingModule.withRoutes([ + { path: 'search', component: DummyComponent, pathMatch: 'full' }, + ]) + ], + declarations: [ + ViewModeSwitchComponent, + DummyComponent + ], + providers: [ + { provide: ItemDataService, useValue: {} }, + SearchService + ], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewModeSwitchComponent); + comp = fixture.componentInstance; // ViewModeSwitchComponent test instance + fixture.detectChanges(); + const debugElements = fixture.debugElement.queryAll(By.css('a')); + listButton = debugElements[0].nativeElement; + gridButton = debugElements[1].nativeElement; + searchService = fixture.debugElement.injector.get(SearchService); + }); + + it('should set list button as active when on list mode', fakeAsync(() => { + searchService.setViewMode(ViewMode.List); + tick(); + fixture.detectChanges(); + expect(comp.currentMode).toBe(ViewMode.List); + expect(listButton.classList).toContain('active'); + expect(gridButton.classList).not.toContain('active'); + })); + + it('should set grid button as active when on grid mode', fakeAsync(() => { + searchService.setViewMode(ViewMode.Grid); + tick(); + fixture.detectChanges(); + expect(comp.currentMode).toBe(ViewMode.Grid); + expect(listButton.classList).not.toContain('active'); + expect(gridButton.classList).toContain('active'); + })); +});