mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #3390 from alexandrevryghem/w2p-117616_ported-custom-atmire-eslint-rules
Enforce DSpace-specific coding conventions
This commit is contained in:
@@ -15,6 +15,10 @@ trim_trailing_whitespace = false
|
|||||||
|
|
||||||
[*.ts]
|
[*.ts]
|
||||||
quote_type = single
|
quote_type = single
|
||||||
|
ij_typescript_enforce_trailing_comma = whenmultiline
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
ij_javascript_enforce_trailing_comma = whenmultiline
|
||||||
|
|
||||||
[*.json5]
|
[*.json5]
|
||||||
ij_json_keep_blank_lines_in_code = 3
|
ij_json_keep_blank_lines_in_code = 3
|
||||||
|
@@ -263,9 +263,48 @@
|
|||||||
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
|
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
|
||||||
|
|
||||||
// Custom DSpace Angular rules
|
// Custom DSpace Angular rules
|
||||||
|
"dspace-angular-ts/alias-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "of",
|
||||||
|
"local": "of"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"dspace-angular-ts/themed-component-classes": "error",
|
"dspace-angular-ts/themed-component-classes": "error",
|
||||||
"dspace-angular-ts/themed-component-selectors": "error",
|
"dspace-angular-ts/themed-component-selectors": "error",
|
||||||
"dspace-angular-ts/themed-component-usages": "error"
|
"dspace-angular-ts/themed-component-usages": "error",
|
||||||
|
"dspace-angular-ts/themed-decorators": [
|
||||||
|
"off",
|
||||||
|
{
|
||||||
|
"decorators": {
|
||||||
|
"listableObjectComponent": 3,
|
||||||
|
"rendersSectionForMenu": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dspace-angular-ts/themed-wrapper-no-input-defaults": "error",
|
||||||
|
"dspace-angular-ts/unique-decorators": [
|
||||||
|
"off",
|
||||||
|
{
|
||||||
|
"decorators": [
|
||||||
|
"listableObjectComponent"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dspace-angular-ts/sort-standalone-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"locale": "en-US",
|
||||||
|
"maxItems": 0,
|
||||||
|
"indent": 2,
|
||||||
|
"trailingComma": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -9,6 +9,8 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
|
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -19,24 +21,28 @@ _______
|
|||||||
```html
|
```html
|
||||||
<button [dsBtnDisabled]="true">Submit</button>
|
<button [dsBtnDisabled]="true">Submit</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### disabled attribute is still valid on non-button elements
|
##### disabled attribute is still valid on non-button elements
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input disabled>
|
<input disabled>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### [disabled] attribute is still valid on non-button elements
|
##### [disabled] attribute is still valid on non-button elements
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input [disabled]="true">
|
<input [disabled]="true">
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### angular dynamic attributes that use disabled are still valid
|
##### angular dynamic attributes that use disabled are still valid
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<button [class.disabled]="isDisabled">Submit</button>
|
<button [class.disabled]="isDisabled">Submit</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +53,9 @@ _______
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<button disabled>Submit</button>
|
<button disabled>Submit</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -63,6 +72,9 @@ Result of `yarn lint --fix`:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<button [disabled]="true">Submit</button>
|
<button [disabled]="true">Submit</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -11,6 +11,8 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
|
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ _______
|
|||||||
<ds-test-themeable></ds-test-themeable>
|
<ds-test-themeable></ds-test-themeable>
|
||||||
<ds-test-themeable [test]="something"></ds-test-themeable>
|
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### use no-prefix selectors in TypeScript templates
|
##### use no-prefix selectors in TypeScript templates
|
||||||
|
|
||||||
@@ -33,6 +36,7 @@ _______
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### use no-prefix selectors in TypeScript test templates
|
##### use no-prefix selectors in TypeScript test templates
|
||||||
|
|
||||||
@@ -45,6 +49,7 @@ Filename: `lint/test/fixture/src/test.spec.ts`
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### base selectors are also allowed in TypeScript test templates
|
##### base selectors are also allowed in TypeScript test templates
|
||||||
|
|
||||||
@@ -57,6 +62,7 @@ Filename: `lint/test/fixture/src/test.spec.ts`
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -69,6 +75,9 @@ class Test {
|
|||||||
<ds-themed-test-themeable/>
|
<ds-themed-test-themeable/>
|
||||||
<ds-themed-test-themeable></ds-themed-test-themeable>
|
<ds-themed-test-themeable></ds-themed-test-themeable>
|
||||||
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -91,6 +100,9 @@ Result of `yarn lint --fix`:
|
|||||||
<ds-base-test-themeable/>
|
<ds-base-test-themeable/>
|
||||||
<ds-base-test-themeable></ds-base-test-themeable>
|
<ds-base-test-themeable></ds-base-test-themeable>
|
||||||
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules
|
[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules
|
||||||
_______
|
_______
|
||||||
|
|
||||||
|
- [`dspace-angular-ts/alias-imports`](./rules/alias-imports.md): Unclear imports should be aliased for clarity
|
||||||
|
- [`dspace-angular-ts/sort-standalone-imports`](./rules/sort-standalone-imports.md): Sorts the standalone `@Component` imports alphabetically
|
||||||
- [`dspace-angular-ts/themed-component-classes`](./rules/themed-component-classes.md): Formatting rules for themeable component classes
|
- [`dspace-angular-ts/themed-component-classes`](./rules/themed-component-classes.md): Formatting rules for themeable component classes
|
||||||
- [`dspace-angular-ts/themed-component-selectors`](./rules/themed-component-selectors.md): Themeable component selectors should follow the DSpace convention
|
- [`dspace-angular-ts/themed-component-selectors`](./rules/themed-component-selectors.md): Themeable component selectors should follow the DSpace convention
|
||||||
- [`dspace-angular-ts/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via their `ThemedComponent` wrapper class
|
- [`dspace-angular-ts/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via their `ThemedComponent` wrapper class
|
||||||
|
- [`dspace-angular-ts/themed-decorators`](./rules/themed-decorators.md): Entry components with theme support should declare the correct theme
|
||||||
|
- [`dspace-angular-ts/themed-wrapper-no-input-defaults`](./rules/themed-wrapper-no-input-defaults.md): ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))
|
||||||
|
- [`dspace-angular-ts/unique-decorators`](./rules/unique-decorators.md): Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)
|
||||||
|
148
docs/lint/ts/rules/alias-imports.md
Normal file
148
docs/lint/ts/rules/alias-imports.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/alias-imports`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Unclear imports should be aliased for clarity
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/alias-imports.ts)
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### `aliases`
|
||||||
|
|
||||||
|
A list of all the imports that you want to alias for clarity. Every alias should be declared in the following format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "of",
|
||||||
|
"local": "observableOf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### correctly aliased imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
```
|
||||||
|
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "of",
|
||||||
|
"local": "observableOf"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### enforce unaliased import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { combineLatest } from 'rxjs';
|
||||||
|
```
|
||||||
|
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "combineLatest",
|
||||||
|
"local": "combineLatest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### imports without alias
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
This import must be aliased
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### imports under the wrong alias
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { of as ofSomething } from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
This import uses the wrong alias (should be {{ local }})
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow aliasing import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "combineLatest",
|
||||||
|
"local": "combineLatest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
This import should not use an alias
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { combineLatest } from 'rxjs';
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
245
docs/lint/ts/rules/sort-standalone-imports.md
Normal file
245
docs/lint/ts/rules/sort-standalone-imports.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/sort-standalone-imports`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Sorts the standalone `@Component` imports alphabetically
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/sort-standalone-imports.ts)
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### `locale`
|
||||||
|
|
||||||
|
The locale used to sort the imports.,
|
||||||
|
#### `maxItems`
|
||||||
|
|
||||||
|
The maximum number of imports that should be displayed before each import is separated onto its own line.,
|
||||||
|
#### `indent`
|
||||||
|
|
||||||
|
The indent used for the project.,
|
||||||
|
#### `trailingComma`
|
||||||
|
|
||||||
|
Whether the last import should have a trailing comma (only applicable for multiline imports).
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### should sort multiple imports on separate lines
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should not inlines singular imports when maxItems is 0
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should inline singular imports when maxItems is 1
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"maxItems": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### should sort multiple imports alphabetically
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
AsyncPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Standalone imports should be sorted alphabetically
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should not put singular imports on one line when maxItems is 0
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Standalone imports should be sorted alphabetically
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should not put singular imports on a separate line when maxItems is 1
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"maxItems": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Standalone imports should be sorted alphabetically
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should not display multiple imports on the same line
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [AsyncPipe, RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Standalone imports should be sorted alphabetically
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -11,6 +11,8 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ _______
|
|||||||
class Something {
|
class Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Base component
|
##### Base component
|
||||||
|
|
||||||
@@ -34,9 +37,10 @@ class Something {
|
|||||||
selector: 'ds-base-test-themable',
|
selector: 'ds-base-test-themable',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class TestThemeableTomponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Wrapper component
|
##### Wrapper component
|
||||||
|
|
||||||
@@ -50,9 +54,10 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
TestThemeableComponent,
|
TestThemeableComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Override component
|
##### Override component
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
class Override extends BaseComponent {
|
class Override extends BaseComponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -80,6 +86,9 @@ class Override extends BaseComponent {
|
|||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -107,6 +116,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -137,6 +149,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -171,6 +186,9 @@ import { SomethingElse } from './somewhere-else';
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -207,6 +225,9 @@ import { Something, SomethingElse } from './somewhere-else';
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -237,6 +258,9 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
})
|
})
|
||||||
class Override extends BaseComponent {
|
class Override extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -17,6 +17,8 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ _______
|
|||||||
class Something {
|
class Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
|
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ class ThemedSomething extends ThemedComponent<Something> {
|
|||||||
class OverrideSomething extends Something {
|
class OverrideSomething extends Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Other themed component wrappers should not interfere
|
##### Other themed component wrappers should not interfere
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ class Something {
|
|||||||
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
|
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -85,6 +90,9 @@ Filename: `lint/test/fixture/src/app/test/test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -111,6 +119,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -137,6 +148,9 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
})
|
})
|
||||||
class TestThememeableComponent extends BaseComponent {
|
class TestThememeableComponent extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -15,6 +15,8 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ const config = {
|
|||||||
b: ChipsComponent,
|
b: ChipsComponent,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow base class in class declaration
|
##### allow base class in class declaration
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ const config = {
|
|||||||
export class TestThemeableComponent {
|
export class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow inheriting from base class
|
##### allow inheriting from base class
|
||||||
|
|
||||||
@@ -46,6 +50,7 @@ import { TestThemeableComponent } from './app/test/test-themeable.component';
|
|||||||
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
|
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow base class in ViewChild
|
##### allow base class in ViewChild
|
||||||
|
|
||||||
@@ -56,6 +61,7 @@ export class Something {
|
|||||||
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
|
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow wrapper selectors in test queries
|
##### allow wrapper selectors in test queries
|
||||||
|
|
||||||
@@ -65,6 +71,7 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
By.css('ds-themeable');
|
By.css('ds-themeable');
|
||||||
By.css('#test > ds-themeable > #nest');
|
By.css('#test > ds-themeable > #nest');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow wrapper selectors in cypress queries
|
##### allow wrapper selectors in cypress queries
|
||||||
|
|
||||||
@@ -74,6 +81,7 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
By.css('ds-themeable');
|
By.css('ds-themeable');
|
||||||
By.css('#test > ds-themeable > #nest');
|
By.css('#test > ds-themeable > #nest');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +98,9 @@ const config = {
|
|||||||
a: TestThemeableComponent,
|
a: TestThemeableComponent,
|
||||||
b: TestComponent,
|
b: TestComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -120,6 +131,9 @@ const config = {
|
|||||||
b: TestComponent,
|
b: TestComponent,
|
||||||
c: Something,
|
c: Something,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -150,6 +164,9 @@ const DECLARATIONS = [
|
|||||||
Something,
|
Something,
|
||||||
ThemedTestThemeableComponent,
|
ThemedTestThemeableComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -173,6 +190,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
By.css('ds-themed-themeable');
|
By.css('ds-themed-themeable');
|
||||||
By.css('#test > ds-themed-themeable > #nest');
|
By.css('#test > ds-themed-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -194,6 +214,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
By.css('ds-base-themeable');
|
By.css('ds-base-themeable');
|
||||||
By.css('#test > ds-base-themeable > #nest');
|
By.css('#test > ds-base-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -215,6 +238,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
cy.get('ds-themed-themeable');
|
cy.get('ds-themed-themeable');
|
||||||
cy.get('#test > ds-themed-themeable > #nest');
|
cy.get('#test > ds-themed-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -236,6 +262,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
cy.get('ds-base-themeable');
|
cy.get('ds-base-themeable');
|
||||||
cy.get('#test > ds-base-themeable > #nest');
|
cy.get('#test > ds-base-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -266,6 +295,9 @@ import { TestThemeableComponent } from '../../../../app/test/test-themeable.comp
|
|||||||
})
|
})
|
||||||
export class UsageComponent {
|
export class UsageComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -306,6 +338,9 @@ import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-t
|
|||||||
})
|
})
|
||||||
export class UsageComponent {
|
export class UsageComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
183
docs/lint/ts/rules/themed-decorators.md
Normal file
183
docs/lint/ts/rules/themed-decorators.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-decorators`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Entry components with theme support should declare the correct theme
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/themed-decorators.ts)
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### `decorators`
|
||||||
|
|
||||||
|
A mapping for all the existing themeable decorators, with the decorator name as the key and the index of the `theme` argument as the value.
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### theme file declares the correct theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### plain file declares no theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### plain file declares explicit undefined theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### test file declares theme outside of theme directory
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.spec.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### only track configured decorators
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@something('test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### theme file declares the wrong theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Wrong theme declaration in decorator
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### plain file declares a theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
There is a theme declaration in decorator, but this file is not part of a theme
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### theme file declares no theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test-2/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
No theme declaration in decorator
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### theme file declares explicit undefined theme in @listableObjectComponent
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test-2/app/dynamic-component/dynamic-component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
No theme declaration in decorator
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
92
docs/lint/ts/rules/themed-wrapper-no-input-defaults.md
Normal file
92
docs/lint/ts/rules/themed-wrapper-no-input-defaults.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-wrapper-no-input-defaults`
|
||||||
|
_______
|
||||||
|
|
||||||
|
ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/themed-wrapper-no-input-defaults.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### ThemedComponent wrapper defines an input without a default value
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Regular class defines an input with a default value
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class Test {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test = 'test';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code
|
||||||
|
|
||||||
|
##### ThemedComponent wrapper defines an input with a default value
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test1 = 'test';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test2 = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test2: number = 123;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test3: number[] = [1,2,3];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
ThemedComponent wrapper declares inputs with defaults
|
||||||
|
ThemedComponent wrapper declares inputs with defaults
|
||||||
|
ThemedComponent wrapper declares inputs with defaults
|
||||||
|
ThemedComponent wrapper declares inputs with defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### ThemedComponent wrapper defines an input with an undefined default value
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
ThemedComponent wrapper declares inputs with defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
86
docs/lint/ts/rules/unique-decorators.md
Normal file
86
docs/lint/ts/rules/unique-decorators.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/unique-decorators`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/unique-decorators.ts)
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### `decorators`
|
||||||
|
|
||||||
|
The list of all the decorators for which you want to enforce this behavior.
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### checked decorator, no repetitions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b')
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3, Enum.TEST1)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3, Enum.TEST2)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### unchecked decorator, some repetitions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@something(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@something(a)
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code
|
||||||
|
|
||||||
|
##### checked decorator, some repetitions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Duplicate decorator call
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -33,6 +33,7 @@ export const info = {
|
|||||||
[Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.',
|
[Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optionDocs: [],
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -45,6 +45,7 @@ The only exception to this rule are unit tests, where we may want to use the bas
|
|||||||
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
|
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optionDocs: [],
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
304
lint/src/rules/ts/alias-imports.ts
Normal file
304
lint/src/rules/ts/alias-imports.ts
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
import {
|
||||||
|
AST_NODE_TYPES,
|
||||||
|
ESLintUtils,
|
||||||
|
TSESLint,
|
||||||
|
TSESTree,
|
||||||
|
} from '@typescript-eslint/utils';
|
||||||
|
import { Scope } from '@typescript-eslint/utils/ts-eslint';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DSpaceESLintRuleInfo,
|
||||||
|
NamedTests,
|
||||||
|
OptionDoc,
|
||||||
|
} from '../../util/structure';
|
||||||
|
|
||||||
|
export enum Message {
|
||||||
|
MISSING_ALIAS = 'missingAlias',
|
||||||
|
WRONG_ALIAS = 'wrongAlias',
|
||||||
|
MULTIPLE_ALIASES = 'multipleAliases',
|
||||||
|
UNNECESSARY_ALIAS = 'unnecessaryAlias',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AliasImportOptions {
|
||||||
|
aliases: AliasImportOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AliasImportOption {
|
||||||
|
package: string;
|
||||||
|
imported: string;
|
||||||
|
local: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AliasImportDocOptions {
|
||||||
|
aliases: OptionDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const info: DSpaceESLintRuleInfo<[AliasImportOptions], [AliasImportDocOptions]> = {
|
||||||
|
name: 'alias-imports',
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'Unclear imports should be aliased for clarity',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
[Message.MISSING_ALIAS]: 'This import must be aliased',
|
||||||
|
[Message.WRONG_ALIAS]: 'This import uses the wrong alias (should be {{ local }})',
|
||||||
|
[Message.MULTIPLE_ALIASES]: 'This import was used twice with a different alias (should be {{ local }})',
|
||||||
|
[Message.UNNECESSARY_ALIAS]: 'This import should not use an alias',
|
||||||
|
},
|
||||||
|
fixable: 'code',
|
||||||
|
type: 'problem',
|
||||||
|
schema: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
package: { type: 'string' },
|
||||||
|
imported: { type: 'string' },
|
||||||
|
local: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optionDocs: [
|
||||||
|
{
|
||||||
|
aliases: {
|
||||||
|
title: '`aliases`',
|
||||||
|
description: `A list of all the imports that you want to alias for clarity. Every alias should be declared in the following format:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"package": "rxjs",
|
||||||
|
"imported": "of",
|
||||||
|
"local": "observableOf"
|
||||||
|
}
|
||||||
|
\`\`\``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
aliases: [
|
||||||
|
{
|
||||||
|
package: 'rxjs',
|
||||||
|
imported: 'of',
|
||||||
|
local: 'observableOf',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = ESLintUtils.RuleCreator.withoutDocs({
|
||||||
|
...info,
|
||||||
|
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
|
||||||
|
return (options[0] as AliasImportOptions).aliases.reduce((selectors: any, option: AliasImportOption) => {
|
||||||
|
selectors[`ImportDeclaration[source.value = "${option.package}"] > ImportSpecifier[imported.name = "${option.imported}"][local.name != "${option.local}"]`] = (node: TSESTree.ImportSpecifier) => handleUnaliasedImport(context, option, node);
|
||||||
|
return selectors;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tests: NamedTests = {
|
||||||
|
plugin: info.name,
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
name: 'correctly aliased imports',
|
||||||
|
code: `
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
aliases: [
|
||||||
|
{
|
||||||
|
package: 'rxjs',
|
||||||
|
imported: 'of',
|
||||||
|
local: 'observableOf',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enforce unaliased import',
|
||||||
|
code: `
|
||||||
|
import { combineLatest } from 'rxjs';
|
||||||
|
`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
aliases: [
|
||||||
|
{
|
||||||
|
package: 'rxjs',
|
||||||
|
imported: 'combineLatest',
|
||||||
|
local: 'combineLatest',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
name: 'imports without alias',
|
||||||
|
code: `
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'missingAlias',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'imports under the wrong alias',
|
||||||
|
code: `
|
||||||
|
import { of as ofSomething } from 'rxjs';
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'wrongAlias',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disallow aliasing import',
|
||||||
|
code: `
|
||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'unnecessaryAlias',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
import { combineLatest } from 'rxjs';
|
||||||
|
`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
aliases: [
|
||||||
|
{
|
||||||
|
package: 'rxjs',
|
||||||
|
imported: 'combineLatest',
|
||||||
|
local: 'combineLatest',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the incorrectly aliased imports with the ones defined in the defaultOptions
|
||||||
|
*
|
||||||
|
* @param context The current {@link TSESLint.RuleContext}
|
||||||
|
* @param option The current {@link AliasImportOption} that needs to be handled
|
||||||
|
* @param node The incorrect import node that should be fixed
|
||||||
|
*/
|
||||||
|
function handleUnaliasedImport(context: TSESLint.RuleContext<Message, unknown[]>, option: AliasImportOption, node: TSESTree.ImportSpecifier): void {
|
||||||
|
const hasCorrectAliasedImport: boolean = (node.parent as TSESTree.ImportDeclaration).specifiers.find((specifier: TSESTree.ImportClause) => specifier.local.name === option.local && specifier.type === AST_NODE_TYPES.ImportSpecifier && (specifier as TSESTree.ImportSpecifier).imported.name === option.imported) !== undefined;
|
||||||
|
if (option.imported === option.local) {
|
||||||
|
if (hasCorrectAliasedImport) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.MULTIPLE_ALIASES,
|
||||||
|
data: { local: option.local },
|
||||||
|
node: node,
|
||||||
|
fix(fixer: TSESLint.RuleFixer) {
|
||||||
|
const fixes: TSESLint.RuleFix[] = [];
|
||||||
|
|
||||||
|
const commaAfter = context.sourceCode.getTokenAfter(node, {
|
||||||
|
filter: (token: TSESTree.Token) => token.value === ',',
|
||||||
|
});
|
||||||
|
if (commaAfter) {
|
||||||
|
fixes.push(fixer.removeRange([node.range[0], commaAfter.range[1]]));
|
||||||
|
} else {
|
||||||
|
fixes.push(fixer.remove(node));
|
||||||
|
}
|
||||||
|
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.UNNECESSARY_ALIAS,
|
||||||
|
data: { local: option.local },
|
||||||
|
node: node,
|
||||||
|
fix(fixer: TSESLint.RuleFixer) {
|
||||||
|
const fixes: TSESLint.RuleFix[] = [];
|
||||||
|
|
||||||
|
fixes.push(fixer.replaceText(node, option.imported));
|
||||||
|
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasCorrectAliasedImport) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.MULTIPLE_ALIASES,
|
||||||
|
data: { local: option.local },
|
||||||
|
node: node,
|
||||||
|
fix(fixer: TSESLint.RuleFixer) {
|
||||||
|
const fixes: TSESLint.RuleFix[] = [];
|
||||||
|
|
||||||
|
const commaAfter = context.sourceCode.getTokenAfter(node, {
|
||||||
|
filter: (token: TSESTree.Token) => token.value === ',',
|
||||||
|
});
|
||||||
|
if (commaAfter) {
|
||||||
|
fixes.push(fixer.removeRange([node.range[0], commaAfter.range[1]]));
|
||||||
|
} else {
|
||||||
|
fixes.push(fixer.remove(node));
|
||||||
|
}
|
||||||
|
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (node.local.name === node.imported.name) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.MISSING_ALIAS,
|
||||||
|
node: node,
|
||||||
|
fix(fixer: TSESLint.RuleFixer) {
|
||||||
|
const fixes: TSESLint.RuleFix[] = [];
|
||||||
|
|
||||||
|
fixes.push(fixer.replaceText(node.local, `${option.imported} as ${option.local}`));
|
||||||
|
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.WRONG_ALIAS,
|
||||||
|
data: { local: option.local },
|
||||||
|
node: node,
|
||||||
|
fix(fixer: TSESLint.RuleFixer) {
|
||||||
|
const fixes: TSESLint.RuleFix[] = [];
|
||||||
|
|
||||||
|
fixes.push(fixer.replaceText(node.local, option.local));
|
||||||
|
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the {@link TSESLint.RuleFix}s for all the usages of the incorrect import.
|
||||||
|
*
|
||||||
|
* @param context The current {@link TSESLint.RuleContext}
|
||||||
|
* @param fixer The instance {@link TSESLint.RuleFixer}
|
||||||
|
* @param node The node which needs to be replaced
|
||||||
|
* @param newAlias The new import name
|
||||||
|
*/
|
||||||
|
function retrieveUsageReplacementFixes(context: TSESLint.RuleContext<Message, unknown[]>, fixer: TSESLint.RuleFixer, node: TSESTree.ImportSpecifier, newAlias: string): TSESLint.RuleFix[] {
|
||||||
|
return context.sourceCode.getDeclaredVariables(node)[0].references.map((reference: Scope.Reference) => fixer.replaceText(reference.identifier, newAlias));
|
||||||
|
}
|
@@ -10,14 +10,24 @@ import {
|
|||||||
RuleExports,
|
RuleExports,
|
||||||
} from '../../util/structure';
|
} from '../../util/structure';
|
||||||
/* eslint-disable import/no-namespace */
|
/* eslint-disable import/no-namespace */
|
||||||
|
import * as aliasImports from './alias-imports';
|
||||||
|
import * as sortStandaloneImports from './sort-standalone-imports';
|
||||||
import * as themedComponentClasses from './themed-component-classes';
|
import * as themedComponentClasses from './themed-component-classes';
|
||||||
import * as themedComponentSelectors from './themed-component-selectors';
|
import * as themedComponentSelectors from './themed-component-selectors';
|
||||||
import * as themedComponentUsages from './themed-component-usages';
|
import * as themedComponentUsages from './themed-component-usages';
|
||||||
|
import * as themedDecorators from './themed-decorators';
|
||||||
|
import * as themedWrapperNoInputDefaults from './themed-wrapper-no-input-defaults';
|
||||||
|
import * as uniqueDecorators from './unique-decorators';
|
||||||
|
|
||||||
const index = [
|
const index = [
|
||||||
|
aliasImports,
|
||||||
|
sortStandaloneImports,
|
||||||
themedComponentClasses,
|
themedComponentClasses,
|
||||||
themedComponentSelectors,
|
themedComponentSelectors,
|
||||||
themedComponentUsages,
|
themedComponentUsages,
|
||||||
|
themedDecorators,
|
||||||
|
themedWrapperNoInputDefaults,
|
||||||
|
uniqueDecorators,
|
||||||
] as unknown as RuleExports[];
|
] as unknown as RuleExports[];
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
|
306
lint/src/rules/ts/sort-standalone-imports.ts
Normal file
306
lint/src/rules/ts/sort-standalone-imports.ts
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
import {
|
||||||
|
ASTUtils as TSESLintASTUtils,
|
||||||
|
ESLintUtils,
|
||||||
|
TSESLint,
|
||||||
|
TSESTree,
|
||||||
|
} from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DSpaceESLintRuleInfo,
|
||||||
|
NamedTests,
|
||||||
|
OptionDoc,
|
||||||
|
} from '../../util/structure';
|
||||||
|
|
||||||
|
const DEFAULT_LOCALE = 'en-US';
|
||||||
|
const DEFAULT_MAX_SIZE = 0;
|
||||||
|
const DEFAULT_SPACE_INDENT_AMOUNT = 2;
|
||||||
|
const DEFAULT_TRAILING_COMMA = true;
|
||||||
|
|
||||||
|
export enum Message {
|
||||||
|
SORT_STANDALONE_IMPORTS_ARRAYS = 'sortStandaloneImportsArrays',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UniqueDecoratorsOptions {
|
||||||
|
locale: string;
|
||||||
|
maxItems: number;
|
||||||
|
indent: number;
|
||||||
|
trailingComma: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UniqueDecoratorsDocOptions {
|
||||||
|
locale: OptionDoc;
|
||||||
|
maxItems: OptionDoc;
|
||||||
|
indent: OptionDoc;
|
||||||
|
trailingComma: OptionDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const info: DSpaceESLintRuleInfo<[UniqueDecoratorsOptions], [UniqueDecoratorsDocOptions]> = {
|
||||||
|
name: 'sort-standalone-imports',
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'Sorts the standalone `@Component` imports alphabetically',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
[Message.SORT_STANDALONE_IMPORTS_ARRAYS]: 'Standalone imports should be sorted alphabetically',
|
||||||
|
},
|
||||||
|
fixable: 'code',
|
||||||
|
type: 'problem',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locale: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
maxItems: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
indent: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
trailingComma: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
optionDocs: [
|
||||||
|
{
|
||||||
|
locale: {
|
||||||
|
title: '`locale`',
|
||||||
|
description: 'The locale used to sort the imports.',
|
||||||
|
},
|
||||||
|
maxItems: {
|
||||||
|
title: '`maxItems`',
|
||||||
|
description: 'The maximum number of imports that should be displayed before each import is separated onto its own line.',
|
||||||
|
},
|
||||||
|
indent: {
|
||||||
|
title: '`indent`',
|
||||||
|
description: 'The indent used for the project.',
|
||||||
|
},
|
||||||
|
trailingComma: {
|
||||||
|
title: '`trailingComma`',
|
||||||
|
description: 'Whether the last import should have a trailing comma (only applicable for multiline imports).',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
locale: DEFAULT_LOCALE,
|
||||||
|
maxItems: DEFAULT_MAX_SIZE,
|
||||||
|
indent: DEFAULT_SPACE_INDENT_AMOUNT,
|
||||||
|
trailingComma: DEFAULT_TRAILING_COMMA,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = ESLintUtils.RuleCreator.withoutDocs({
|
||||||
|
...info,
|
||||||
|
create(context: TSESLint.RuleContext<Message, unknown[]>, [{ locale, maxItems, indent, trailingComma }]: any) {
|
||||||
|
return {
|
||||||
|
['ClassDeclaration > Decorator > CallExpression[callee.name="Component"] > ObjectExpression > Property[key.name="imports"] > ArrayExpression']: (node: TSESTree.ArrayExpression) => {
|
||||||
|
const elements = node.elements.filter((element) => element !== null && (TSESLintASTUtils.isIdentifier(element) || element?.type === TSESTree.AST_NODE_TYPES.CallExpression));
|
||||||
|
const sortedNames: string[] = elements
|
||||||
|
.map((element) => context.sourceCode.getText(element!))
|
||||||
|
.sort((a: string, b: string) => a.localeCompare(b, locale));
|
||||||
|
|
||||||
|
const isSorted: boolean = elements.every((identifier, index) => context.sourceCode.getText(identifier!) === sortedNames[index]);
|
||||||
|
|
||||||
|
const requiresMultiline: boolean = maxItems < node.elements.length;
|
||||||
|
const isMultiline: boolean = /\n/.test(context.sourceCode.getText(node));
|
||||||
|
|
||||||
|
const incorrectFormat: boolean = requiresMultiline !== isMultiline;
|
||||||
|
|
||||||
|
if (isSorted && !incorrectFormat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.report({
|
||||||
|
node: node.parent,
|
||||||
|
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
|
||||||
|
fix: (fixer: TSESLint.RuleFixer) => {
|
||||||
|
if (requiresMultiline) {
|
||||||
|
const multilineImports: string = sortedNames
|
||||||
|
.map((name: string) => `${' '.repeat(2 * indent)}${name}${trailingComma ? ',' : ''}`)
|
||||||
|
.join(trailingComma ? '\n' : ',\n');
|
||||||
|
|
||||||
|
return fixer.replaceText(node, `[\n${multilineImports}\n${' '.repeat(indent)}]`);
|
||||||
|
} else {
|
||||||
|
return fixer.replaceText(node, `[${sortedNames.join(', ')}]`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tests: NamedTests = {
|
||||||
|
plugin: info.name,
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
name: 'should sort multiple imports on separate lines',
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not inlines singular imports when maxItems is 0',
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should inline singular imports when maxItems is 1',
|
||||||
|
options: [{ maxItems: 1 }],
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
name: 'should sort multiple imports alphabetically',
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
AsyncPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not put singular imports on one line when maxItems is 0',
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not put singular imports on a separate line when maxItems is 1',
|
||||||
|
options: [{ maxItems: 1 }],
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not display multiple imports on the same line',
|
||||||
|
code: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [AsyncPipe, RootComponent],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-app',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RootComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppComponent {}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@@ -52,6 +52,7 @@ export const info = {
|
|||||||
[Message.WRAPPER_IMPORTS_BASE]: 'Themed component wrapper classes must only import the base class',
|
[Message.WRAPPER_IMPORTS_BASE]: 'Themed component wrapper classes must only import the base class',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optionDocs: [],
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ class Something {
|
|||||||
selector: 'ds-base-test-themable',
|
selector: 'ds-base-test-themable',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class TestThemeableTomponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
@@ -195,7 +196,7 @@ class TestThemeableTomponent {
|
|||||||
TestThemeableComponent,
|
TestThemeableComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
@@ -53,6 +53,7 @@ Unit tests are exempt from this rule, because they may redefine components using
|
|||||||
[Message.THEMED]: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
|
[Message.THEMED]: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optionDocs: [],
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -63,6 +63,7 @@ There are a few exceptions where the base class can still be used:
|
|||||||
[Message.BASE_IN_MODULE]: 'Base themeable components shouldn\'t be declared in modules',
|
[Message.BASE_IN_MODULE]: 'Base themeable components shouldn\'t be declared in modules',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optionDocs: [],
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
280
lint/src/rules/ts/themed-decorators.ts
Normal file
280
lint/src/rules/ts/themed-decorators.ts
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
import {
|
||||||
|
AST_NODE_TYPES,
|
||||||
|
ESLintUtils,
|
||||||
|
TSESLint,
|
||||||
|
TSESTree,
|
||||||
|
} from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { fixture } from '../../../test/fixture';
|
||||||
|
import { isTestFile } from '../../util/filter';
|
||||||
|
import {
|
||||||
|
DSpaceESLintRuleInfo,
|
||||||
|
NamedTests,
|
||||||
|
OptionDoc,
|
||||||
|
} from '../../util/structure';
|
||||||
|
import { getFileTheme } from '../../util/theme-support';
|
||||||
|
|
||||||
|
export enum Message {
|
||||||
|
NO_THEME_DECLARED_IN_THEME_FILE = 'noThemeDeclaredInThemeFile',
|
||||||
|
THEME_DECLARED_IN_NON_THEME_FILE = 'themeDeclaredInNonThemeFile',
|
||||||
|
WRONG_THEME_DECLARED_IN_THEME_FILE = 'wrongThemeDeclaredInThemeFile',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemedDecoratorsOption {
|
||||||
|
decorators: { [name: string]: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemedDecoratorsDocsOption {
|
||||||
|
decorators: OptionDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const info: DSpaceESLintRuleInfo<[ThemedDecoratorsOption], [ThemedDecoratorsDocsOption]> = {
|
||||||
|
name: 'themed-decorators',
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'Entry components with theme support should declare the correct theme',
|
||||||
|
},
|
||||||
|
fixable: 'code',
|
||||||
|
messages: {
|
||||||
|
[Message.NO_THEME_DECLARED_IN_THEME_FILE]: 'No theme declaration in decorator',
|
||||||
|
[Message.THEME_DECLARED_IN_NON_THEME_FILE]: 'There is a theme declaration in decorator, but this file is not part of a theme',
|
||||||
|
[Message.WRONG_THEME_DECLARED_IN_THEME_FILE]: 'Wrong theme declaration in decorator',
|
||||||
|
},
|
||||||
|
type: 'problem',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
decorators: {
|
||||||
|
type: 'object',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
optionDocs: [
|
||||||
|
{
|
||||||
|
decorators: {
|
||||||
|
title: '`decorators`',
|
||||||
|
description: 'A mapping for all the existing themeable decorators, with the decorator name as the key and the index of the `theme` argument as the value.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
decorators: {
|
||||||
|
listableObjectComponent: 3,
|
||||||
|
rendersSectionForMenu: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = ESLintUtils.RuleCreator.withoutDocs({
|
||||||
|
...info,
|
||||||
|
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
|
||||||
|
return {
|
||||||
|
[`ClassDeclaration > Decorator > CallExpression[callee.name=/^(${Object.keys(options[0].decorators).join('|')})$/]`]: (node: TSESTree.CallExpression) => {
|
||||||
|
if (isTestFile(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
|
||||||
|
// We only support regular method identifiers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileTheme = getFileTheme(context);
|
||||||
|
const themeDeclaration = getDeclaredTheme(options, node as TSESTree.CallExpression);
|
||||||
|
|
||||||
|
if (themeDeclaration === undefined) {
|
||||||
|
if (fileTheme !== undefined) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.NO_THEME_DECLARED_IN_THEME_FILE,
|
||||||
|
node: node,
|
||||||
|
fix(fixer) {
|
||||||
|
return fixer.insertTextAfter(node.arguments[node.arguments.length - 1], `, '${fileTheme as string}'`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (themeDeclaration?.type === AST_NODE_TYPES.Literal) {
|
||||||
|
if (fileTheme === undefined) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.THEME_DECLARED_IN_NON_THEME_FILE,
|
||||||
|
node: themeDeclaration,
|
||||||
|
fix(fixer) {
|
||||||
|
const idx = node.arguments.findIndex((v) => v.range === themeDeclaration.range);
|
||||||
|
|
||||||
|
if (idx === 0) {
|
||||||
|
return fixer.remove(themeDeclaration);
|
||||||
|
} else {
|
||||||
|
const previousArgument = node.arguments[idx - 1];
|
||||||
|
return fixer.removeRange([previousArgument.range[1], themeDeclaration.range[1]]); // todo: comma?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (fileTheme !== themeDeclaration?.value) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.WRONG_THEME_DECLARED_IN_THEME_FILE,
|
||||||
|
node: themeDeclaration,
|
||||||
|
fix(fixer) {
|
||||||
|
return fixer.replaceText(themeDeclaration, `'${fileTheme as string}'`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (themeDeclaration?.type === AST_NODE_TYPES.Identifier && themeDeclaration.name === 'undefined') {
|
||||||
|
if (fileTheme !== undefined) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.NO_THEME_DECLARED_IN_THEME_FILE,
|
||||||
|
node: node,
|
||||||
|
fix(fixer) {
|
||||||
|
return fixer.replaceText(node.arguments[node.arguments.length - 1], `'${fileTheme as string}'`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected theme declaration');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tests: NamedTests = {
|
||||||
|
plugin: info.name,
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
name: 'theme file declares the correct theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/themes/test/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plain file declares no theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plain file declares explicit undefined theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'test file declares theme outside of theme directory',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/app/dynamic-component/dynamic-component.spec.ts'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'only track configured decorators',
|
||||||
|
code: `
|
||||||
|
@something('test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
name: 'theme file declares the wrong theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/themes/test/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'wrongThemeDeclaredInThemeFile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plain file declares a theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'themeDeclaredInNonThemeFile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'theme file declares no theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/themes/test-2/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'noThemeDeclaredInThemeFile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'theme file declares explicit undefined theme in @listableObjectComponent',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, undefined)
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
filename: fixture('src/themes/test-2/app/dynamic-component/dynamic-component.ts'),
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'noThemeDeclaredInThemeFile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
|
||||||
|
export class Something extends SomethingElse {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDeclaredTheme(options: [ThemedDecoratorsOption], decoratorCall: TSESTree.CallExpression): TSESTree.Node | undefined {
|
||||||
|
const index: number = options[0].decorators[(decoratorCall.callee as TSESTree.Identifier).name];
|
||||||
|
|
||||||
|
if (decoratorCall.arguments.length >= index + 1) {
|
||||||
|
return decoratorCall.arguments[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
158
lint/src/rules/ts/themed-wrapper-no-input-defaults.ts
Normal file
158
lint/src/rules/ts/themed-wrapper-no-input-defaults.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import {
|
||||||
|
ESLintUtils,
|
||||||
|
TSESTree,
|
||||||
|
} from '@typescript-eslint/utils';
|
||||||
|
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DSpaceESLintRuleInfo,
|
||||||
|
NamedTests,
|
||||||
|
} from '../../util/structure';
|
||||||
|
import { isThemedComponentWrapper } from '../../util/theme-support';
|
||||||
|
|
||||||
|
export enum Message {
|
||||||
|
WRAPPER_HAS_INPUT_DEFAULTS = 'wrapperHasInputDefaults',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const info: DSpaceESLintRuleInfo = {
|
||||||
|
name: 'themed-wrapper-no-input-defaults',
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
[Message.WRAPPER_HAS_INPUT_DEFAULTS]: 'ThemedComponent wrapper declares inputs with defaults',
|
||||||
|
},
|
||||||
|
type: 'problem',
|
||||||
|
schema: [],
|
||||||
|
},
|
||||||
|
optionDocs: [],
|
||||||
|
defaultOptions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = ESLintUtils.RuleCreator.withoutDocs({
|
||||||
|
...info,
|
||||||
|
create(context: RuleContext<any, any>, options: any) {
|
||||||
|
return {
|
||||||
|
'ClassBody > PropertyDefinition > Decorator > CallExpression[callee.name=\'Input\']': (node: TSESTree.CallExpression) => {
|
||||||
|
const classDeclaration = (node?.parent?.parent?.parent as TSESTree.Decorator); // todo: clean this up
|
||||||
|
if (!isThemedComponentWrapper(classDeclaration)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyDefinition: TSESTree.PropertyDefinition = (node.parent.parent as any); // todo: clean this up
|
||||||
|
|
||||||
|
if (propertyDefinition.value !== null) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.WRAPPER_HAS_INPUT_DEFAULTS,
|
||||||
|
node: propertyDefinition.value,
|
||||||
|
// fix(fixer) {
|
||||||
|
// // todo: don't strip type annotations!
|
||||||
|
// // todo: replace default with appropriate type annotation if not present!
|
||||||
|
// return fixer.removeRange([propertyDefinition.key.range[1], (propertyDefinition.value as any).range[1]]);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tests: NamedTests = {
|
||||||
|
plugin: info.name,
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
name: 'ThemedComponent wrapper defines an input without a default value',
|
||||||
|
code: `
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Regular class defines an input with a default value',
|
||||||
|
code: `
|
||||||
|
export class Test {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test = 'test';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
name: 'ThemedComponent wrapper defines an input with a default value',
|
||||||
|
code: `
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test1 = 'test';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test2 = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test2: number = 123;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test3: number[] = [1,2,3];
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'wrapperHasInputDefaults',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messageId: 'wrapperHasInputDefaults',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messageId: 'wrapperHasInputDefaults',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messageId: 'wrapperHasInputDefaults',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// output: `
|
||||||
|
// export class TTest extends ThemedComponent<Test> {
|
||||||
|
//
|
||||||
|
// @Input()
|
||||||
|
// test1: string;
|
||||||
|
//
|
||||||
|
// @Input()
|
||||||
|
// test2: boolean;
|
||||||
|
//
|
||||||
|
// @Input()
|
||||||
|
// test2: number;
|
||||||
|
//
|
||||||
|
// @Input()
|
||||||
|
// test3: number[];
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ThemedComponent wrapper defines an input with an undefined default value',
|
||||||
|
code: `
|
||||||
|
export class TTest extends ThemedComponent<Test> {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
test = undefined;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'wrapperHasInputDefaults',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// output: `
|
||||||
|
// export class TTest extends ThemedComponent<Test> {
|
||||||
|
//
|
||||||
|
// @Input()
|
||||||
|
// test;
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
226
lint/src/rules/ts/unique-decorators.ts
Normal file
226
lint/src/rules/ts/unique-decorators.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import {
|
||||||
|
AST_NODE_TYPES,
|
||||||
|
ESLintUtils,
|
||||||
|
TSESLint,
|
||||||
|
TSESTree,
|
||||||
|
} from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { isTestFile } from '../../util/filter';
|
||||||
|
import {
|
||||||
|
DSpaceESLintRuleInfo,
|
||||||
|
NamedTests,
|
||||||
|
OptionDoc,
|
||||||
|
} from '../../util/structure';
|
||||||
|
|
||||||
|
export enum Message {
|
||||||
|
DUPLICATE_DECORATOR_CALL = 'duplicateDecoratorCall',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the decorators by decoratorName → file → Set<String>
|
||||||
|
*/
|
||||||
|
const decoratorCalls: Map<string, Map<string, Set<string>>> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep a list of the files wo contain a decorator. This is done in order to prevent the `Program` selector from being
|
||||||
|
* run for every file.
|
||||||
|
*/
|
||||||
|
const fileWithDecorators: Set<string> = new Set();
|
||||||
|
|
||||||
|
export interface UniqueDecoratorsOptions {
|
||||||
|
decorators: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UniqueDecoratorsDocOptions {
|
||||||
|
decorators: OptionDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const info: DSpaceESLintRuleInfo<[UniqueDecoratorsOptions], [UniqueDecoratorsDocOptions]> = {
|
||||||
|
name: 'unique-decorators',
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
[Message.DUPLICATE_DECORATOR_CALL]: 'Duplicate decorator call',
|
||||||
|
},
|
||||||
|
type: 'problem',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
decorators: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
optionDocs: [
|
||||||
|
{
|
||||||
|
decorators: {
|
||||||
|
title: '`decorators`',
|
||||||
|
description: 'The list of all the decorators for which you want to enforce this behavior.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
decorators: [
|
||||||
|
'listableObjectComponent', // todo: must take default arguments into account!
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = ESLintUtils.RuleCreator.withoutDocs({
|
||||||
|
...info,
|
||||||
|
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
['Program']: () => {
|
||||||
|
if (fileWithDecorators.has(context.physicalFilename)) {
|
||||||
|
for (const decorator of options[0].decorators) {
|
||||||
|
decoratorCalls.get(decorator)?.get(context.physicalFilename)?.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`ClassDeclaration > Decorator > CallExpression[callee.name=/^(${options[0].decorators.join('|')})$/]`]: (node: TSESTree.CallExpression) => {
|
||||||
|
if (isTestFile(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
|
||||||
|
// We only support regular method identifiers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileWithDecorators.add(context.physicalFilename);
|
||||||
|
|
||||||
|
if (!isUnique(node, context.physicalFilename)) {
|
||||||
|
context.report({
|
||||||
|
messageId: Message.DUPLICATE_DECORATOR_CALL,
|
||||||
|
node: node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tests: NamedTests = {
|
||||||
|
plugin: info.name,
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
name: 'checked decorator, no repetitions',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b')
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3, Enum.TEST1)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a, 'b', 3, Enum.TEST2)
|
||||||
|
export class C {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'unchecked decorator, some repetitions',
|
||||||
|
code: `
|
||||||
|
@something(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@something(a)
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
name: 'checked decorator, some repetitions',
|
||||||
|
code: `
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@listableObjectComponent(a)
|
||||||
|
export class B {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'duplicateDecoratorCall',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function callKey(node: TSESTree.CallExpression): string {
|
||||||
|
let key = '';
|
||||||
|
|
||||||
|
for (const arg of node.arguments) {
|
||||||
|
switch ((arg as TSESTree.Node).type) {
|
||||||
|
// todo: can we make this more generic somehow?
|
||||||
|
case AST_NODE_TYPES.Identifier:
|
||||||
|
key += (arg as TSESTree.Identifier).name;
|
||||||
|
break;
|
||||||
|
case AST_NODE_TYPES.Literal:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||||
|
key += (arg as TSESTree.Literal).value;
|
||||||
|
break;
|
||||||
|
case AST_NODE_TYPES.MemberExpression:
|
||||||
|
key += (arg as any).object.name + '.' + (arg as any).property.name;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized decorator argument type: ${arg.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
key += ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnique(node: TSESTree.CallExpression, filePath: string): boolean {
|
||||||
|
const decorator = (node.callee as TSESTree.Identifier).name;
|
||||||
|
|
||||||
|
if (!decoratorCalls.has(decorator)) {
|
||||||
|
decoratorCalls.set(decorator, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decoratorCalls.get(decorator)!.has(filePath)) {
|
||||||
|
decoratorCalls.get(decorator)!.set(filePath, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = callKey(node);
|
||||||
|
|
||||||
|
let unique = true;
|
||||||
|
|
||||||
|
for (const decoratorCallsByFile of decoratorCalls.get(decorator)!.values()) {
|
||||||
|
if (decoratorCallsByFile.has(key)) {
|
||||||
|
unique = !unique;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decoratorCalls.get(decorator)?.get(filePath)?.add(key);
|
||||||
|
|
||||||
|
return unique;
|
||||||
|
}
|
10
lint/src/util/filter.ts
Normal file
10
lint/src/util/filter.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the current file is a test file
|
||||||
|
* @param context the current ESLint rule context
|
||||||
|
*/
|
||||||
|
export function isTestFile(context: RuleContext<any, any>): boolean {
|
||||||
|
// note: shouldn't use plain .filename (doesn't work in DSpace Angular 7.4)
|
||||||
|
return context.getFilename()?.endsWith('.spec.ts') ;
|
||||||
|
}
|
@@ -17,10 +17,16 @@ export type Meta = RuleMetaData<string, unknown[]>;
|
|||||||
export type Valid = ValidTestCase<unknown[]>;
|
export type Valid = ValidTestCase<unknown[]>;
|
||||||
export type Invalid = InvalidTestCase<string, unknown[]>;
|
export type Invalid = InvalidTestCase<string, unknown[]>;
|
||||||
|
|
||||||
export interface DSpaceESLintRuleInfo {
|
export interface DSpaceESLintRuleInfo<T = unknown[], D = unknown[]> {
|
||||||
name: string;
|
name: string;
|
||||||
meta: Meta,
|
meta: Meta,
|
||||||
defaultOptions: unknown[],
|
optionDocs: D,
|
||||||
|
defaultOptions: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptionDoc {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NamedTests {
|
export interface NamedTests {
|
||||||
|
@@ -7,6 +7,11 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/<%- plugin.name.replace('dspace-angular-', '') %>/<%- rule.name %>.ts)
|
[Source code](../../../../lint/src/rules/<%- plugin.name.replace('dspace-angular-', '') %>/<%- rule.name %>.ts)
|
||||||
|
|
||||||
|
<% if (rule.optionDocs?.length > 0) { %>
|
||||||
|
### Options
|
||||||
|
<%- rule.optionDocs.map(optionDoc => Object.keys(optionDoc).map(option => '\n#### ' + optionDoc[option].title + '\n\n' + optionDoc[option].description)) %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
<% if (tests.valid) {%>
|
<% if (tests.valid) {%>
|
||||||
@@ -19,6 +24,13 @@ Filename: `<%- test.filename %>`
|
|||||||
```<%- plugin.language.toLowerCase() %>
|
```<%- plugin.language.toLowerCase() %>
|
||||||
<%- test.code.trim() %>
|
<%- test.code.trim() %>
|
||||||
```
|
```
|
||||||
|
<% if (test?.options?.length > 0) { %>
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
<%- JSON.stringify(test.options[0], null, 2) %>
|
||||||
|
```
|
||||||
|
<% }%>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
@@ -31,6 +43,15 @@ Filename: `<%- test.filename %>`
|
|||||||
<% } %>
|
<% } %>
|
||||||
```<%- plugin.language.toLowerCase() %>
|
```<%- plugin.language.toLowerCase() %>
|
||||||
<%- test.code.trim() %>
|
<%- test.code.trim() %>
|
||||||
|
|
||||||
|
<% if (test?.options?.length > 0) { %>
|
||||||
|
With options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
<%- JSON.stringify(test.options[0], null, 2) %>
|
||||||
|
```
|
||||||
|
<% }%>
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TSESTree } from '@typescript-eslint/utils';
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
|
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { basename } from 'path';
|
import { basename } from 'path';
|
||||||
import ts, { Identifier } from 'typescript';
|
import ts, { Identifier } from 'typescript';
|
||||||
@@ -263,3 +264,18 @@ export const DISALLOWED_THEME_SELECTORS = 'ds-(base|themed)-';
|
|||||||
export function fixSelectors(text: string): string {
|
export function fixSelectors(text: string): string {
|
||||||
return text.replaceAll(/ds-(base|themed)-/g, 'ds-');
|
return text.replaceAll(/ds-(base|themed)-/g, 'ds-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the theme of the current file based on its path in the project.
|
||||||
|
* @param context the current ESLint rule context
|
||||||
|
*/
|
||||||
|
export function getFileTheme(context: RuleContext<any, any>): string | undefined {
|
||||||
|
// note: shouldn't use plain .filename (doesn't work in DSpace Angular 7.4)
|
||||||
|
const m = context.getFilename()?.match(/\/src\/themes\/([^/]+)\//);
|
||||||
|
|
||||||
|
if (m?.length === 2) {
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { RuleMetaData } from '@typescript-eslint/utils/ts-eslint';
|
||||||
|
|
||||||
import { default as html } from '../src/rules/html';
|
import { default as html } from '../src/rules/html';
|
||||||
import { default as ts } from '../src/rules/ts';
|
import { default as ts } from '../src/rules/ts';
|
||||||
|
|
||||||
@@ -69,6 +71,16 @@ describe('plugin structure', () => {
|
|||||||
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
|
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
|
||||||
expect(ruleExports.tests.invalid.length).toBeGreaterThan(0);
|
expect(ruleExports.tests.invalid.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should contain a valid ESLint rule', () => {
|
||||||
|
// we don't have a better way to enforce this, but it's something at least
|
||||||
|
expect((ruleExports.rule as any).name).toBeUndefined(
|
||||||
|
'Rules should be passed to RuleCreator, omitting info.name since it is not part of the RuleWithMeta interface',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ruleExports.rule.create).toBeTruthy();
|
||||||
|
expect(ruleExports.rule.meta).toEqual(ruleExports.info.meta as RuleMetaData<string, []>);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -51,16 +51,16 @@ import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe';
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
PaginationComponent,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgbAccordionModule,
|
|
||||||
TranslateModule,
|
|
||||||
NgbNavModule,
|
|
||||||
ThemedSearchComponent,
|
|
||||||
BrowserOnlyPipe,
|
BrowserOnlyPipe,
|
||||||
NgxPaginationModule,
|
|
||||||
SelectableListItemControlComponent,
|
|
||||||
ListableObjectComponentLoaderComponent,
|
ListableObjectComponentLoaderComponent,
|
||||||
|
NgbAccordionModule,
|
||||||
|
NgbNavModule,
|
||||||
|
NgxPaginationModule,
|
||||||
|
PaginationComponent,
|
||||||
|
SelectableListItemControlComponent,
|
||||||
|
ThemedSearchComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -26,10 +26,10 @@ import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.com
|
|||||||
templateUrl: './bulk-access.component.html',
|
templateUrl: './bulk-access.component.html',
|
||||||
styleUrls: ['./bulk-access.component.scss'],
|
styleUrls: ['./bulk-access.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
BulkAccessSettingsComponent,
|
|
||||||
BulkAccessBrowseComponent,
|
|
||||||
BtnDisabledDirective,
|
BtnDisabledDirective,
|
||||||
|
BulkAccessBrowseComponent,
|
||||||
|
BulkAccessSettingsComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
@@ -14,9 +14,9 @@ import { AccessControlFormContainerComponent } from '../../../shared/access-cont
|
|||||||
styleUrls: ['./bulk-access-settings.component.scss'],
|
styleUrls: ['./bulk-access-settings.component.scss'],
|
||||||
exportAs: 'dsBulkSettings',
|
exportAs: 'dsBulkSettings',
|
||||||
imports: [
|
imports: [
|
||||||
|
AccessControlFormContainerComponent,
|
||||||
NgbAccordionModule,
|
NgbAccordionModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
AccessControlFormContainerComponent,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -27,7 +27,7 @@ import {
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
@@ -85,7 +85,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
}), this.allEpeople));
|
}), this.allEpeople));
|
||||||
},
|
},
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return observableOf(this.activeEPerson);
|
return of(this.activeEPerson);
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (scope === 'email') {
|
if (scope === 'email') {
|
||||||
@@ -129,7 +129,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
||||||
return (ePerson2.uuid !== ePerson.uuid);
|
return (ePerson2.uuid !== ePerson.uuid);
|
||||||
});
|
});
|
||||||
return observableOf(true);
|
return of(true);
|
||||||
},
|
},
|
||||||
editEPerson(ePerson: EPerson) {
|
editEPerson(ePerson: EPerson) {
|
||||||
this.activeEPerson = ePerson;
|
this.activeEPerson = ePerson;
|
||||||
@@ -145,7 +145,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true),
|
isAuthorized: of(true),
|
||||||
});
|
});
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
fixture = TestBed.createComponent(EPeopleRegistryComponent);
|
fixture = TestBed.createComponent(EPeopleRegistryComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
modalService = TestBed.inject(NgbModal);
|
modalService = TestBed.inject(NgbModal);
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should hide delete EPerson button when the isAuthorized returns false', () => {
|
it('should hide delete EPerson button when the isAuthorized returns false', () => {
|
||||||
spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false));
|
spyOn(authorizationService, 'isAuthorized').and.returnValue(of(false));
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
@@ -67,14 +67,14 @@ import { EPersonFormComponent } from './eperson-form/eperson-form.component';
|
|||||||
selector: 'ds-epeople-registry',
|
selector: 'ds-epeople-registry',
|
||||||
templateUrl: './epeople-registry.component.html',
|
templateUrl: './epeople-registry.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
RouterModule,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
EPersonFormComponent,
|
EPersonFormComponent,
|
||||||
ReactiveFormsModule,
|
|
||||||
ThemedLoadingComponent,
|
|
||||||
PaginationComponent,
|
|
||||||
NgClass,
|
NgClass,
|
||||||
|
PaginationComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterModule,
|
||||||
|
ThemedLoadingComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -25,7 +25,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthService } from '../../../core/auth/auth.service';
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
@@ -91,7 +91,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return observableOf(this.activeEPerson);
|
return of(this.activeEPerson);
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (scope === 'email') {
|
if (scope === 'email') {
|
||||||
@@ -115,7 +115,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
||||||
return (ePerson2.uuid !== ePerson.uuid);
|
return (ePerson2.uuid !== ePerson.uuid);
|
||||||
});
|
});
|
||||||
return observableOf(true);
|
return of(true);
|
||||||
},
|
},
|
||||||
create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
||||||
this.allEpeople = [...this.allEpeople, ePerson];
|
this.allEpeople = [...this.allEpeople, ePerson];
|
||||||
@@ -210,7 +210,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
authService = new AuthServiceStub();
|
authService = new AuthServiceStub();
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true),
|
isAuthorized: of(true),
|
||||||
|
|
||||||
});
|
});
|
||||||
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
||||||
@@ -389,7 +389,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
describe('without active EPerson', () => {
|
describe('without active EPerson', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(undefined));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(undefined));
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -429,7 +429,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(expectedWithId));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -485,10 +485,10 @@ describe('EPersonFormComponent', () => {
|
|||||||
spyOn(authService, 'impersonate').and.callThrough();
|
spyOn(authService, 'impersonate').and.callThrough();
|
||||||
eperson = EPersonMock;
|
eperson = EPersonMock;
|
||||||
component.epersonInitial = eperson;
|
component.epersonInitial = eperson;
|
||||||
component.canDelete$ = observableOf(true);
|
component.canDelete$ = of(true);
|
||||||
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson));
|
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(of(eperson));
|
||||||
modalService = (component as any).modalService;
|
modalService = (component as any).modalService;
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -499,7 +499,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
|
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
|
||||||
component.canDelete$ = observableOf(false);
|
component.canDelete$ = of(false);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
|
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
|
||||||
expect(deleteButton).toBeNull();
|
expect(deleteButton).toBeNull();
|
||||||
|
@@ -27,7 +27,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -78,14 +78,14 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
|||||||
selector: 'ds-eperson-form',
|
selector: 'ds-eperson-form',
|
||||||
templateUrl: './eperson-form.component.html',
|
templateUrl: './eperson-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
FormComponent,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
TranslateModule,
|
BtnDisabledDirective,
|
||||||
ThemedLoadingComponent,
|
FormComponent,
|
||||||
|
HasNoValuePipe,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
HasNoValuePipe,
|
ThemedLoadingComponent,
|
||||||
BtnDisabledDirective,
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -357,7 +357,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.groups$ = this.activeEPerson$.pipe(
|
this.groups$ = this.activeEPerson$.pipe(
|
||||||
switchMap((eperson) => {
|
switchMap((eperson) => {
|
||||||
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
return observableCombineLatest([of(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
elementsPerPage: this.config.pageSize,
|
elementsPerPage: this.config.pageSize,
|
||||||
})]);
|
})]);
|
||||||
@@ -366,7 +366,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
if (eperson != null) {
|
if (eperson != null) {
|
||||||
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
||||||
}
|
}
|
||||||
return observableOf(undefined);
|
return of(undefined);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -379,14 +379,14 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
if (hasValue(eperson)) {
|
if (hasValue(eperson)) {
|
||||||
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
||||||
} else {
|
} else {
|
||||||
return observableOf(false);
|
return of(false);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.canDelete$ = this.activeEPerson$.pipe(
|
this.canDelete$ = this.activeEPerson$.pipe(
|
||||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)),
|
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)),
|
||||||
);
|
);
|
||||||
this.canReset$ = observableOf(true);
|
this.canReset$ = of(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -540,16 +540,16 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
take(1),
|
take(1),
|
||||||
switchMap((confirm: boolean) => {
|
switchMap((confirm: boolean) => {
|
||||||
if (confirm && hasValue(eperson.id)) {
|
if (confirm && hasValue(eperson.id)) {
|
||||||
this.canDelete$ = observableOf(false);
|
this.canDelete$ = of(false);
|
||||||
return this.epersonService.deleteEPerson(eperson).pipe(
|
return this.epersonService.deleteEPerson(eperson).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson })),
|
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson })),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return observableOf(null);
|
return of(null);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
finalize(() => this.canDelete$ = observableOf(true)),
|
finalize(() => this.canDelete$ = of(true)),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
|
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
|
||||||
|
@@ -27,7 +27,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
@@ -116,7 +116,7 @@ describe('GroupFormComponent', () => {
|
|||||||
activeGroup: null,
|
activeGroup: null,
|
||||||
createdGroup: null,
|
createdGroup: null,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(this.activeGroup);
|
return of(this.activeGroup);
|
||||||
},
|
},
|
||||||
getGroupRegistryRouterLink(): string {
|
getGroupRegistryRouterLink(): string {
|
||||||
return '/access-control/groups';
|
return '/access-control/groups';
|
||||||
@@ -137,7 +137,7 @@ describe('GroupFormComponent', () => {
|
|||||||
this.activeGroup = null;
|
this.activeGroup = null;
|
||||||
},
|
},
|
||||||
findById(id: string) {
|
findById(id: string) {
|
||||||
return observableOf({ payload: null, hasSucceeded: true });
|
return of({ payload: null, hasSucceeded: true });
|
||||||
},
|
},
|
||||||
findByHref(href: string) {
|
findByHref(href: string) {
|
||||||
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
||||||
@@ -164,7 +164,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true),
|
isAuthorized: of(true),
|
||||||
});
|
});
|
||||||
dsoDataServiceStub = {
|
dsoDataServiceStub = {
|
||||||
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
@@ -330,7 +330,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
|
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(of(expected));
|
||||||
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
});
|
});
|
||||||
@@ -417,7 +417,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(observableOf(expected));
|
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(of(expected));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
@@ -471,11 +471,11 @@ describe('GroupFormComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
||||||
component.activeGroup$ = observableOf({
|
component.activeGroup$ = of({
|
||||||
id: 'active-group',
|
id: 'active-group',
|
||||||
permanent: false,
|
permanent: false,
|
||||||
} as Group);
|
} as Group);
|
||||||
component.canEdit$ = observableOf(true);
|
component.canEdit$ = of(true);
|
||||||
|
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
|
|
||||||
|
@@ -87,13 +87,13 @@ import { ValidateGroupExists } from './validators/group-exists.validator';
|
|||||||
selector: 'ds-group-form',
|
selector: 'ds-group-form',
|
||||||
templateUrl: './group-form.component.html',
|
templateUrl: './group-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
FormComponent,
|
|
||||||
AlertComponent,
|
AlertComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
TranslateModule,
|
|
||||||
ContextHelpDirective,
|
ContextHelpDirective,
|
||||||
|
FormComponent,
|
||||||
MembersListComponent,
|
MembersListComponent,
|
||||||
SubgroupsListComponent,
|
SubgroupsListComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -32,7 +32,7 @@ import {
|
|||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
@@ -113,7 +113,7 @@ describe('MembersListComponent', () => {
|
|||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
epersonNonMembers: epersonNonMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(activeGroup);
|
return of(activeGroup);
|
||||||
},
|
},
|
||||||
getEPersonMembers() {
|
getEPersonMembers() {
|
||||||
return this.epersonMembers;
|
return this.epersonMembers;
|
||||||
@@ -127,7 +127,7 @@ describe('MembersListComponent', () => {
|
|||||||
this.epersonNonMembers.splice(index, 1);
|
this.epersonNonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return of(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
// empty
|
// empty
|
||||||
@@ -147,7 +147,7 @@ describe('MembersListComponent', () => {
|
|||||||
});
|
});
|
||||||
// Add eperson to list of non-members
|
// Add eperson to list of non-members
|
||||||
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return of(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
|
@@ -25,7 +25,7 @@ import {
|
|||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
ObservedValueOf,
|
ObservedValueOf,
|
||||||
of as observableOf,
|
of,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -103,14 +103,14 @@ export interface EPersonListActionConfig {
|
|||||||
selector: 'ds-members-list',
|
selector: 'ds-members-list',
|
||||||
templateUrl: './members-list.component.html',
|
templateUrl: './members-list.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
ContextHelpDirective,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
PaginationComponent,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
RouterLink,
|
|
||||||
NgClass,
|
|
||||||
BtnDisabledDirective,
|
BtnDisabledDirective,
|
||||||
|
ContextHelpDirective,
|
||||||
|
NgClass,
|
||||||
|
PaginationComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -260,7 +260,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
||||||
*/
|
*/
|
||||||
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
||||||
return observableOf(true);
|
return of(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -31,7 +31,7 @@ import {
|
|||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
|
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
subgroups: subgroups,
|
subgroups: subgroups,
|
||||||
groupNonMembers: groupNonMembers,
|
groupNonMembers: groupNonMembers,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(this.activeGroup);
|
return of(this.activeGroup);
|
||||||
},
|
},
|
||||||
getSubgroups(): Group {
|
getSubgroups(): Group {
|
||||||
return this.subgroups;
|
return this.subgroups;
|
||||||
@@ -138,7 +138,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
this.groupNonMembers.splice(index, 1);
|
this.groupNonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return of(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
// empty
|
// empty
|
||||||
@@ -155,7 +155,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
});
|
});
|
||||||
// Add group to list of non-members
|
// Add group to list of non-members
|
||||||
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
|
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return of(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
routerStub = new RouterMock();
|
routerStub = new RouterMock();
|
||||||
|
@@ -59,12 +59,12 @@ enum SubKey {
|
|||||||
selector: 'ds-subgroups-list',
|
selector: 'ds-subgroups-list',
|
||||||
templateUrl: './subgroups-list.component.html',
|
templateUrl: './subgroups-list.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
ContextHelpDirective,
|
ContextHelpDirective,
|
||||||
TranslateModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
@@ -37,7 +37,7 @@ describe('GroupPageGuard', () => {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
|
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
|
||||||
( halEndpointService as any ).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl));
|
( halEndpointService as any ).getEndpoint.and.returnValue(of(groupsEndpointUrl));
|
||||||
|
|
||||||
authorizationService = jasmine.createSpyObj(['isAuthorized']);
|
authorizationService = jasmine.createSpyObj(['isAuthorized']);
|
||||||
// NOTE: value is set in beforeEach
|
// NOTE: value is set in beforeEach
|
||||||
@@ -46,7 +46,7 @@ describe('GroupPageGuard', () => {
|
|||||||
( router as any ).parseUrl.and.returnValue = {};
|
( router as any ).parseUrl.and.returnValue = {};
|
||||||
|
|
||||||
authService = jasmine.createSpyObj(['isAuthenticated']);
|
authService = jasmine.createSpyObj(['isAuthenticated']);
|
||||||
( authService as any ).isAuthenticated.and.returnValue(observableOf(true));
|
( authService as any ).isAuthenticated.and.returnValue(of(true));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -69,7 +69,7 @@ describe('GroupPageGuard', () => {
|
|||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
describe('when the current user can manage the group', () => {
|
describe('when the current user can manage the group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
( authorizationService as any ).isAuthorized.and.returnValue(observableOf(true));
|
( authorizationService as any ).isAuthorized.and.returnValue(of(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', (done) => {
|
it('should return true', (done) => {
|
||||||
@@ -89,7 +89,7 @@ describe('GroupPageGuard', () => {
|
|||||||
|
|
||||||
describe('when the current user can not manage the group', () => {
|
describe('when the current user can not manage the group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(authorizationService as any).isAuthorized.and.returnValue(observableOf(false));
|
(authorizationService as any).isAuthorized.and.returnValue(of(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return true', (done) => {
|
it('should not return true', (done) => {
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -33,6 +33,6 @@ export const groupPageGuard = (
|
|||||||
getObjectUrl = defaultGroupPageGetObjectUrl,
|
getObjectUrl = defaultGroupPageGetObjectUrl,
|
||||||
getEPersonUuid?: StringGuardParamFn,
|
getEPersonUuid?: StringGuardParamFn,
|
||||||
): CanActivateFn => someFeatureAuthorizationGuard(
|
): CanActivateFn => someFeatureAuthorizationGuard(
|
||||||
() => observableOf([FeatureID.CanManageGroup]),
|
() => of([FeatureID.CanManageGroup]),
|
||||||
getObjectUrl,
|
getObjectUrl,
|
||||||
getEPersonUuid);
|
getEPersonUuid);
|
||||||
|
@@ -25,7 +25,6 @@ import { provideMockStore } from '@ngrx/store/testing';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
|
||||||
of,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
@@ -95,11 +94,11 @@ describe('GroupsRegistryComponent', () => {
|
|||||||
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
|
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
|
||||||
switch (featureId) {
|
switch (featureId) {
|
||||||
case FeatureID.AdministratorOf:
|
case FeatureID.AdministratorOf:
|
||||||
return observableOf(isAdmin);
|
return of(isAdmin);
|
||||||
case FeatureID.CanManageGroup:
|
case FeatureID.CanManageGroup:
|
||||||
return observableOf(canManageGroup);
|
return of(canManageGroup);
|
||||||
case FeatureID.CanDelete:
|
case FeatureID.CanDelete:
|
||||||
return observableOf(true);
|
return of(true);
|
||||||
default:
|
default:
|
||||||
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
|
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -68,14 +68,14 @@ import { followLink } from '../../shared/utils/follow-link-config.model';
|
|||||||
selector: 'ds-groups-registry',
|
selector: 'ds-groups-registry',
|
||||||
templateUrl: './groups-registry.component.html',
|
templateUrl: './groups-registry.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
BtnDisabledDirective,
|
||||||
|
NgbTooltipModule,
|
||||||
|
PaginationComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
RouterLink,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
AsyncPipe,
|
|
||||||
PaginationComponent,
|
|
||||||
NgbTooltipModule,
|
|
||||||
BtnDisabledDirective,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -179,7 +179,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
switchMap((groups: PaginatedList<Group>) => {
|
switchMap((groups: PaginatedList<Group>) => {
|
||||||
if (groups.page.length === 0) {
|
if (groups.page.length === 0) {
|
||||||
return observableOf(buildPaginatedList(groups.pageInfo, []));
|
return of(buildPaginatedList(groups.pageInfo, []));
|
||||||
}
|
}
|
||||||
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
||||||
switchMap((isSiteAdmin: boolean) => {
|
switchMap((isSiteAdmin: boolean) => {
|
||||||
@@ -224,7 +224,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
|
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
|
||||||
if (isSiteAdmin) {
|
if (isSiteAdmin) {
|
||||||
return observableOf(true);
|
return of(true);
|
||||||
} else {
|
} else {
|
||||||
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
|
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
|
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
map((rd: RemoteData<DSpaceObject>) => hasValue(rd) && hasValue(rd.payload)),
|
map((rd: RemoteData<DSpaceObject>) => hasValue(rd) && hasValue(rd.payload)),
|
||||||
catchError(() => observableOf(false)),
|
catchError(() => of(false)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,10 +33,10 @@ import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzo
|
|||||||
selector: 'ds-batch-import-page',
|
selector: 'ds-batch-import-page',
|
||||||
templateUrl: './batch-import-page.component.html',
|
templateUrl: './batch-import-page.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
FormsModule,
|
|
||||||
UiSwitchModule,
|
|
||||||
FileDropzoneNoUploaderComponent,
|
FileDropzoneNoUploaderComponent,
|
||||||
|
FormsModule,
|
||||||
|
TranslateModule,
|
||||||
|
UiSwitchModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -10,7 +10,9 @@ import { MetadataImportPageComponent } from './metadata-import-page.component';
|
|||||||
selector: 'ds-metadata-import-page',
|
selector: 'ds-metadata-import-page',
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MetadataImportPageComponent],
|
imports: [
|
||||||
|
MetadataImportPageComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ThemedMetadataImportPageComponent extends ThemedComponent<MetadataImportPageComponent> {
|
export class ThemedMetadataImportPageComponent extends ThemedComponent<MetadataImportPageComponent> {
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
|
@@ -30,10 +30,7 @@ import {
|
|||||||
TranslateService,
|
TranslateService,
|
||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import { PaginationService } from 'ngx-pagination';
|
import { PaginationService } from 'ngx-pagination';
|
||||||
import {
|
import { of } from 'rxjs';
|
||||||
of as observableOf,
|
|
||||||
of,
|
|
||||||
} from 'rxjs';
|
|
||||||
|
|
||||||
import { RouteService } from '../../../core/services/route.service';
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
import { MockActivatedRoute } from '../../../shared/mocks/active-router.mock';
|
import { MockActivatedRoute } from '../../../shared/mocks/active-router.mock';
|
||||||
@@ -94,8 +91,8 @@ describe('LdnServiceFormEditComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ldnServicesService = jasmine.createSpyObj('ldnServicesService', {
|
ldnServicesService = jasmine.createSpyObj('ldnServicesService', {
|
||||||
create: observableOf(null),
|
create: of(null),
|
||||||
update: observableOf(null),
|
update: of(null),
|
||||||
findById: createSuccessfulRemoteDataObject$({}),
|
findById: createSuccessfulRemoteDataObject$({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -71,10 +71,10 @@ import { notifyPatterns } from '../ldn-services-patterns/ldn-service-coar-patter
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
NgbDropdownModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
NgbDropdownModule,
|
|
||||||
AsyncPipe,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class LdnServiceFormComponent implements OnInit, OnDestroy {
|
export class LdnServiceFormComponent implements OnInit, OnDestroy {
|
||||||
|
@@ -2,7 +2,7 @@ import {
|
|||||||
cold,
|
cold,
|
||||||
getTestScheduler,
|
getTestScheduler,
|
||||||
} from 'jasmine-marbles';
|
} from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||||
@@ -71,12 +71,12 @@ describe('LdnServicesService test', () => {
|
|||||||
generateRequestId: requestUUID,
|
generateRequestId: requestUUID,
|
||||||
send: true,
|
send: true,
|
||||||
removeByHrefSubstring: {},
|
removeByHrefSubstring: {},
|
||||||
getByHref: observableOf(responseCacheEntry),
|
getByHref: of(responseCacheEntry),
|
||||||
getByUUID: observableOf(responseCacheEntry),
|
getByUUID: of(responseCacheEntry),
|
||||||
});
|
});
|
||||||
|
|
||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getEndpoint: observableOf(endpointURL),
|
getEndpoint: of(endpointURL),
|
||||||
});
|
});
|
||||||
|
|
||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
@@ -107,7 +107,7 @@ describe('LdnServicesService test', () => {
|
|||||||
it('should find service by inbound pattern', (done) => {
|
it('should find service by inbound pattern', (done) => {
|
||||||
const params = [new RequestParam('pattern', 'testPattern')];
|
const params = [new RequestParam('pattern', 'testPattern')];
|
||||||
const findListOptions = Object.assign(new FindListOptions(), {}, { searchParams: params });
|
const findListOptions = Object.assign(new FindListOptions(), {}, { searchParams: params });
|
||||||
spyOn(service, 'searchBy').and.returnValue(observableOf(null));
|
spyOn(service, 'searchBy').and.returnValue(of(null));
|
||||||
spyOn((service as any).searchData, 'searchBy').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([mockLdnService])));
|
spyOn((service as any).searchData, 'searchBy').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([mockLdnService])));
|
||||||
|
|
||||||
service.findByInboundPattern('testPattern').subscribe(() => {
|
service.findByInboundPattern('testPattern').subscribe(() => {
|
||||||
@@ -120,7 +120,7 @@ describe('LdnServicesService test', () => {
|
|||||||
const constraints = [{ void: true }];
|
const constraints = [{ void: true }];
|
||||||
const files = [new File([],'fileName')];
|
const files = [new File([],'fileName')];
|
||||||
spyOn(service as any, 'getInvocationFormData');
|
spyOn(service as any, 'getInvocationFormData');
|
||||||
spyOn(service, 'getBrowseEndpoint').and.returnValue(observableOf('testEndpoint'));
|
spyOn(service, 'getBrowseEndpoint').and.returnValue(of('testEndpoint'));
|
||||||
service.invoke('serviceName', 'serviceId', constraints, files).subscribe(result => {
|
service.invoke('serviceName', 'serviceId', constraints, files).subscribe(result => {
|
||||||
expect((service as any).getInvocationFormData).toHaveBeenCalledWith(constraints, files);
|
expect((service as any).getInvocationFormData).toHaveBeenCalledWith(constraints, files);
|
||||||
expect(service.getBrowseEndpoint).toHaveBeenCalled();
|
expect(service.getBrowseEndpoint).toHaveBeenCalled();
|
||||||
|
@@ -52,13 +52,13 @@ import { LdnService } from '../ldn-services-model/ldn-services.model';
|
|||||||
styleUrls: ['./ldn-services-directory.component.scss'],
|
styleUrls: ['./ldn-services-directory.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
NgClass,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
TruncatableComponent,
|
TruncatableComponent,
|
||||||
TruncatablePartComponent,
|
TruncatablePartComponent,
|
||||||
NgClass,
|
|
||||||
RouterLink,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -6,7 +6,9 @@ import { SuggestionSourcesComponent } from '../../../notifications/suggestions/s
|
|||||||
selector: 'ds-admin-notifications-publication-claim-page',
|
selector: 'ds-admin-notifications-publication-claim-page',
|
||||||
templateUrl: './admin-notifications-publication-claim-page.component.html',
|
templateUrl: './admin-notifications-publication-claim-page.component.html',
|
||||||
styleUrls: ['./admin-notifications-publication-claim-page.component.scss'],
|
styleUrls: ['./admin-notifications-publication-claim-page.component.scss'],
|
||||||
imports: [ SuggestionSourcesComponent ],
|
imports: [
|
||||||
|
SuggestionSourcesComponent,
|
||||||
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class AdminNotificationsPublicationClaimPageComponent {
|
export class AdminNotificationsPublicationClaimPageComponent {
|
||||||
|
@@ -42,9 +42,9 @@ import {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
AdminNotifyMetricsComponent,
|
AdminNotifyMetricsComponent,
|
||||||
|
AsyncPipe,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
AsyncPipe,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -20,8 +20,8 @@ import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admi
|
|||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink,
|
|
||||||
AdminNotifyLogsResultComponent,
|
AdminNotifyLogsResultComponent,
|
||||||
|
RouterLink,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@@ -32,9 +32,9 @@ import { ThemedSearchComponent } from '../../../../shared/search/themed-search.c
|
|||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
SearchLabelsComponent,
|
SearchLabelsComponent,
|
||||||
ThemedSearchComponent,
|
ThemedSearchComponent,
|
||||||
AsyncPipe,
|
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@@ -20,8 +20,8 @@ import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admi
|
|||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink,
|
|
||||||
AdminNotifyLogsResultComponent,
|
AdminNotifyLogsResultComponent,
|
||||||
|
RouterLink,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@@ -7,10 +7,7 @@ import {
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import { of } from 'rxjs';
|
||||||
of as observableOf,
|
|
||||||
of,
|
|
||||||
} from 'rxjs';
|
|
||||||
|
|
||||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
||||||
@@ -120,7 +117,7 @@ describe('AdminNotifySearchResultComponent', () => {
|
|||||||
fixture = TestBed.createComponent(AdminNotifySearchResultComponent);
|
fixture = TestBed.createComponent(AdminNotifySearchResultComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
modalService = TestBed.inject(NgbModal);
|
modalService = TestBed.inject(NgbModal);
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -39,12 +39,12 @@ import { AdminNotifyMessagesService } from '../services/admin-notify-messages.se
|
|||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
DatePipe,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
DatePipe,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
TruncatableComponent,
|
TruncatableComponent,
|
||||||
TruncatablePartComponent,
|
TruncatablePartComponent,
|
||||||
RouterLink,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
|
@@ -9,7 +9,7 @@ import { Router } from '@angular/router';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
||||||
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
|
||||||
@@ -51,7 +51,7 @@ describe('AddBitstreamFormatComponent', () => {
|
|||||||
notificationService = new NotificationsServiceStub();
|
notificationService = new NotificationsServiceStub();
|
||||||
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
|
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
|
||||||
createBitstreamFormat: createSuccessfulRemoteDataObject$({}),
|
createBitstreamFormat: createSuccessfulRemoteDataObject$({}),
|
||||||
clearBitStreamFormatRequests: observableOf(null),
|
clearBitStreamFormatRequests: of(null),
|
||||||
});
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -98,7 +98,7 @@ describe('AddBitstreamFormatComponent', () => {
|
|||||||
notificationService = new NotificationsServiceStub();
|
notificationService = new NotificationsServiceStub();
|
||||||
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
|
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
|
||||||
createBitstreamFormat: createFailedRemoteDataObject$('Error', 500),
|
createBitstreamFormat: createFailedRemoteDataObject$('Error', 500),
|
||||||
clearBitStreamFormatRequests: observableOf(null),
|
clearBitStreamFormatRequests: of(null),
|
||||||
});
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@@ -9,7 +9,7 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { hot } from 'jasmine-marbles';
|
import { hot } from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
@@ -88,14 +88,14 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: of(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
getSelectedBitstreamFormats: hot('a', { a: mockFormatsList }),
|
getSelectedBitstreamFormats: hot('a', { a: mockFormatsList }),
|
||||||
selectBitstreamFormat: {},
|
selectBitstreamFormat: {},
|
||||||
deselectBitstreamFormat: {},
|
deselectBitstreamFormat: {},
|
||||||
deselectAllBitstreamFormats: {},
|
deselectAllBitstreamFormats: {},
|
||||||
delete: createSuccessfulRemoteDataObject$({}),
|
delete: createSuccessfulRemoteDataObject$({}),
|
||||||
clearBitStreamFormatRequests: observableOf('cleared'),
|
clearBitStreamFormatRequests: of('cleared'),
|
||||||
});
|
});
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
paginationService = new PaginationServiceStub();
|
||||||
@@ -225,14 +225,14 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: of(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
getSelectedBitstreamFormats: observableOf(mockFormatsList),
|
getSelectedBitstreamFormats: of(mockFormatsList),
|
||||||
selectBitstreamFormat: {},
|
selectBitstreamFormat: {},
|
||||||
deselectBitstreamFormat: {},
|
deselectBitstreamFormat: {},
|
||||||
deselectAllBitstreamFormats: {},
|
deselectAllBitstreamFormats: {},
|
||||||
delete: createNoContentRemoteDataObject$(),
|
delete: createNoContentRemoteDataObject$(),
|
||||||
clearBitStreamFormatRequests: observableOf('cleared'),
|
clearBitStreamFormatRequests: of('cleared'),
|
||||||
});
|
});
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
paginationService = new PaginationServiceStub();
|
||||||
@@ -282,14 +282,14 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: of(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
getSelectedBitstreamFormats: observableOf(mockFormatsList),
|
getSelectedBitstreamFormats: of(mockFormatsList),
|
||||||
selectBitstreamFormat: {},
|
selectBitstreamFormat: {},
|
||||||
deselectBitstreamFormat: {},
|
deselectBitstreamFormat: {},
|
||||||
deselectAllBitstreamFormats: {},
|
deselectAllBitstreamFormats: {},
|
||||||
delete: createFailedRemoteDataObject$(),
|
delete: createFailedRemoteDataObject$(),
|
||||||
clearBitStreamFormatRequests: observableOf('cleared'),
|
clearBitStreamFormatRequests: of('cleared'),
|
||||||
});
|
});
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
paginationService = new PaginationServiceStub();
|
||||||
|
@@ -38,9 +38,9 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
|||||||
templateUrl: './bitstream-formats.component.html',
|
templateUrl: './bitstream-formats.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
PaginationComponent,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -12,7 +12,7 @@ import {
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -44,7 +44,7 @@ describe('EditBitstreamFormatComponent', () => {
|
|||||||
bitstreamFormat.extensions = null;
|
bitstreamFormat.extensions = null;
|
||||||
|
|
||||||
const routeStub = {
|
const routeStub = {
|
||||||
data: observableOf({
|
data: of({
|
||||||
bitstreamFormat: createSuccessfulRemoteDataObject(bitstreamFormat),
|
bitstreamFormat: createSuccessfulRemoteDataObject(bitstreamFormat),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@@ -30,9 +30,9 @@ import { FormatFormComponent } from '../format-form/format-form.component';
|
|||||||
selector: 'ds-edit-bitstream-format',
|
selector: 'ds-edit-bitstream-format',
|
||||||
templateUrl: './edit-bitstream-format.component.html',
|
templateUrl: './edit-bitstream-format.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
FormatFormComponent,
|
FormatFormComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
AsyncPipe,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -14,7 +14,7 @@ import { RouterLink } from '@angular/router';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { FormBuilderService } from 'src/app/shared/form/builder/form-builder.service';
|
import { FormBuilderService } from 'src/app/shared/form/builder/form-builder.service';
|
||||||
|
|
||||||
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
||||||
@@ -178,7 +178,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected schema when clicked again', waitForAsync(() => {
|
it('should cancel editing the selected schema when clicked again', waitForAsync(() => {
|
||||||
comp.activeMetadataSchema$ = observableOf(mockSchemasList[0] as MetadataSchema);
|
comp.activeMetadataSchema$ = of(mockSchemasList[0] as MetadataSchema);
|
||||||
spyOn(registryService, 'cancelEditMetadataSchema');
|
spyOn(registryService, 'cancelEditMetadataSchema');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -193,7 +193,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
||||||
comp.selectedMetadataSchemaIDs$ = observableOf(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id));
|
comp.selectedMetadataSchemaIDs$ = of(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id));
|
||||||
comp.deleteSchemas();
|
comp.deleteSchemas();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -43,12 +43,12 @@ import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-sch
|
|||||||
templateUrl: './metadata-registry.component.html',
|
templateUrl: './metadata-registry.component.html',
|
||||||
styleUrls: ['./metadata-registry.component.scss'],
|
styleUrls: ['./metadata-registry.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
MetadataSchemaFormComponent,
|
|
||||||
TranslateModule,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
PaginationComponent,
|
MetadataSchemaFormComponent,
|
||||||
NgClass,
|
NgClass,
|
||||||
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
||||||
import { RegistryService } from '../../../../core/registry/registry.service';
|
import { RegistryService } from '../../../../core/registry/registry.service';
|
||||||
@@ -72,7 +72,7 @@ describe('MetadataSchemaFormComponent', () => {
|
|||||||
|
|
||||||
describe('without an active schema', () => {
|
describe('without an active schema', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.activeMetadataSchema$ = observableOf(undefined);
|
component.activeMetadataSchema$ = of(undefined);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -91,7 +91,7 @@ describe('MetadataSchemaFormComponent', () => {
|
|||||||
} as MetadataSchema);
|
} as MetadataSchema);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.activeMetadataSchema$ = observableOf(expectedWithId);
|
component.activeMetadataSchema$ = of(expectedWithId);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -37,8 +37,8 @@ import { FormComponent } from '../../../../shared/form/form.component';
|
|||||||
templateUrl: './metadata-schema-form.component.html',
|
templateUrl: './metadata-schema-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
TranslateModule,
|
|
||||||
FormComponent,
|
FormComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
||||||
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
||||||
@@ -86,7 +86,7 @@ describe('MetadataFieldFormComponent', () => {
|
|||||||
|
|
||||||
describe('without an active field', () => {
|
describe('without an active field', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(undefined));
|
spyOn(registryService, 'getActiveMetadataField').and.returnValue(of(undefined));
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -107,7 +107,7 @@ describe('MetadataFieldFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(expectedWithId));
|
spyOn(registryService, 'getActiveMetadataField').and.returnValue(of(expectedWithId));
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -32,9 +32,9 @@ import { FormComponent } from '../../../../shared/form/form.component';
|
|||||||
selector: 'ds-metadata-field-form',
|
selector: 'ds-metadata-field-form',
|
||||||
templateUrl: './metadata-field-form.component.html',
|
templateUrl: './metadata-field-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
FormComponent,
|
FormComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
AsyncPipe,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -11,7 +11,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
||||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
@@ -224,7 +224,7 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected field when clicked again', waitForAsync(() => {
|
it('should cancel editing the selected field when clicked again', waitForAsync(() => {
|
||||||
comp.activeField$ = observableOf(mockFieldsList[2] as MetadataField);
|
comp.activeField$ = of(mockFieldsList[2] as MetadataField);
|
||||||
spyOn(registryService, 'cancelEditMetadataField');
|
spyOn(registryService, 'cancelEditMetadataField');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -239,7 +239,7 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
||||||
comp.selectedMetadataFieldIDs$ = observableOf(selectedFields.map((metadataField: MetadataField) => metadataField.id));
|
comp.selectedMetadataFieldIDs$ = of(selectedFields.map((metadataField: MetadataField) => metadataField.id));
|
||||||
comp.deleteFields();
|
comp.deleteFields();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of,
|
||||||
Subscription,
|
Subscription,
|
||||||
zip,
|
zip,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
@@ -53,12 +53,12 @@ import { MetadataFieldFormComponent } from './metadata-field-form/metadata-field
|
|||||||
styleUrls: ['./metadata-schema.component.scss'],
|
styleUrls: ['./metadata-schema.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
VarDirective,
|
|
||||||
MetadataFieldFormComponent,
|
MetadataFieldFormComponent,
|
||||||
TranslateModule,
|
|
||||||
PaginationComponent,
|
|
||||||
NgClass,
|
NgClass,
|
||||||
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
VarDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -126,7 +126,7 @@ export class MetadataSchemaComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
private updateFields() {
|
private updateFields() {
|
||||||
this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
||||||
switchMap((currentPagination) => combineLatest([this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination)])),
|
switchMap((currentPagination) => combineLatest([this.metadataSchema$, this.needsUpdate$, of(currentPagination)])),
|
||||||
switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => {
|
switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => {
|
||||||
if (update) {
|
if (update) {
|
||||||
this.needsUpdate$.next(false);
|
this.needsUpdate$.next(false);
|
||||||
|
@@ -18,7 +18,7 @@ import {
|
|||||||
TranslateLoader,
|
TranslateLoader,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
|
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
|
||||||
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
|
||||||
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
|
||||||
@@ -80,7 +80,7 @@ describe('FiltersComponent', () => {
|
|||||||
|
|
||||||
describe('toggle', () => {
|
describe('toggle', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(component, 'getFilteredCollections').and.returnValue(observableOf(expected));
|
spyOn(component, 'getFilteredCollections').and.returnValue(of(expected));
|
||||||
spyOn(component.results, 'deserialize');
|
spyOn(component.results, 'deserialize');
|
||||||
spyOn(component.accordionComponent, 'expand').and.callThrough();
|
spyOn(component.accordionComponent, 'expand').and.callThrough();
|
||||||
component.submit();
|
component.submit();
|
||||||
|
@@ -30,10 +30,10 @@ import { FilteredCollections } from './filtered-collections.model';
|
|||||||
templateUrl: './filtered-collections.component.html',
|
templateUrl: './filtered-collections.component.html',
|
||||||
styleUrls: ['./filtered-collections.component.scss'],
|
styleUrls: ['./filtered-collections.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule,
|
|
||||||
NgbAccordionModule,
|
|
||||||
FiltersComponent,
|
FiltersComponent,
|
||||||
KeyValuePipe,
|
KeyValuePipe,
|
||||||
|
NgbAccordionModule,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -11,7 +11,7 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||||
@@ -59,7 +59,7 @@ describe('FilteredItemsExportCsvComponent', () => {
|
|||||||
invoke: createSuccessfulRemoteDataObject$(process),
|
invoke: createSuccessfulRemoteDataObject$(process),
|
||||||
});
|
});
|
||||||
authorizationDataService = jasmine.createSpyObj('authorizationService', {
|
authorizationDataService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true),
|
isAuthorized: of(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
notificationsService = new NotificationsServiceStub();
|
notificationsService = new NotificationsServiceStub();
|
||||||
@@ -110,7 +110,7 @@ describe('FilteredItemsExportCsvComponent', () => {
|
|||||||
describe('when the user is not an admin', () => {
|
describe('when the user is not an admin', () => {
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
initBeforeEachAsync();
|
initBeforeEachAsync();
|
||||||
(authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
(authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(of(false));
|
||||||
}));
|
}));
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
initBeforeEach();
|
initBeforeEach();
|
||||||
|
@@ -35,7 +35,11 @@ import { QueryPredicate } from '../query-predicate.model';
|
|||||||
styleUrls: ['./filtered-items-export-csv.component.scss'],
|
styleUrls: ['./filtered-items-export-csv.component.scss'],
|
||||||
templateUrl: './filtered-items-export-csv.component.html',
|
templateUrl: './filtered-items-export-csv.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgbTooltipModule, AsyncPipe, TranslateModule],
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
NgbTooltipModule,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Display a button to export the MetadataQuery (aka Filtered Items) Report results as csv
|
* Display a button to export the MetadataQuery (aka Filtered Items) Report results as csv
|
||||||
|
@@ -61,14 +61,14 @@ import { QueryPredicate } from './query-predicate.model';
|
|||||||
templateUrl: './filtered-items.component.html',
|
templateUrl: './filtered-items.component.html',
|
||||||
styleUrls: ['./filtered-items.component.scss'],
|
styleUrls: ['./filtered-items.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
|
||||||
NgbAccordionModule,
|
|
||||||
TranslateModule,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
FiltersComponent,
|
|
||||||
BtnDisabledDirective,
|
BtnDisabledDirective,
|
||||||
FilteredItemsExportCsvComponent,
|
FilteredItemsExportCsvComponent,
|
||||||
|
FiltersComponent,
|
||||||
|
NgbAccordionModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
|
TranslateModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -8,7 +8,9 @@ import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed
|
|||||||
templateUrl: './admin-search-page.component.html',
|
templateUrl: './admin-search-page.component.html',
|
||||||
styleUrls: ['./admin-search-page.component.scss'],
|
styleUrls: ['./admin-search-page.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ThemedConfigurationSearchPageComponent],
|
imports: [
|
||||||
|
ThemedConfigurationSearchPageComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -19,7 +19,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
|||||||
styleUrls: ['./collection-admin-search-result-grid-element.component.scss'],
|
styleUrls: ['./collection-admin-search-result-grid-element.component.scss'],
|
||||||
templateUrl: './collection-admin-search-result-grid-element.component.html',
|
templateUrl: './collection-admin-search-result-grid-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CollectionSearchResultGridElementComponent, RouterLink],
|
imports: [
|
||||||
|
CollectionSearchResultGridElementComponent,
|
||||||
|
RouterLink,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for a collection search result on the admin search page
|
* The component for displaying a list element for a collection search result on the admin search page
|
||||||
|
@@ -19,7 +19,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
|||||||
styleUrls: ['./community-admin-search-result-grid-element.component.scss'],
|
styleUrls: ['./community-admin-search-result-grid-element.component.scss'],
|
||||||
templateUrl: './community-admin-search-result-grid-element.component.html',
|
templateUrl: './community-admin-search-result-grid-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommunitySearchResultGridElementComponent, RouterLink],
|
imports: [
|
||||||
|
CommunitySearchResultGridElementComponent,
|
||||||
|
RouterLink,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for a community search result on the admin search page
|
* The component for displaying a list element for a community search result on the admin search page
|
||||||
|
@@ -31,7 +31,10 @@ import { ItemAdminSearchResultActionsComponent } from '../../item-admin-search-r
|
|||||||
styleUrls: ['./item-admin-search-result-grid-element.component.scss'],
|
styleUrls: ['./item-admin-search-result-grid-element.component.scss'],
|
||||||
templateUrl: './item-admin-search-result-grid-element.component.html',
|
templateUrl: './item-admin-search-result-grid-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ItemAdminSearchResultActionsComponent, DynamicComponentLoaderDirective],
|
imports: [
|
||||||
|
DynamicComponentLoaderDirective,
|
||||||
|
ItemAdminSearchResultActionsComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for an item search result on the admin search page
|
* The component for displaying a list element for an item search result on the admin search page
|
||||||
|
@@ -20,7 +20,11 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l
|
|||||||
styleUrls: ['./collection-admin-search-result-list-element.component.scss'],
|
styleUrls: ['./collection-admin-search-result-list-element.component.scss'],
|
||||||
templateUrl: './collection-admin-search-result-list-element.component.html',
|
templateUrl: './collection-admin-search-result-list-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CollectionSearchResultListElementComponent, RouterLink, TranslateModule],
|
imports: [
|
||||||
|
CollectionSearchResultListElementComponent,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for a collection search result on the admin search page
|
* The component for displaying a list element for a collection search result on the admin search page
|
||||||
|
@@ -20,7 +20,11 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l
|
|||||||
styleUrls: ['./community-admin-search-result-list-element.component.scss'],
|
styleUrls: ['./community-admin-search-result-list-element.component.scss'],
|
||||||
templateUrl: './community-admin-search-result-list-element.component.html',
|
templateUrl: './community-admin-search-result-list-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommunitySearchResultListElementComponent, RouterLink, TranslateModule],
|
imports: [
|
||||||
|
CommunitySearchResultListElementComponent,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for a community search result on the admin search page
|
* The component for displaying a list element for a community search result on the admin search page
|
||||||
|
@@ -15,7 +15,10 @@ import { ItemAdminSearchResultActionsComponent } from '../../item-admin-search-r
|
|||||||
styleUrls: ['./item-admin-search-result-list-element.component.scss'],
|
styleUrls: ['./item-admin-search-result-list-element.component.scss'],
|
||||||
templateUrl: './item-admin-search-result-list-element.component.html',
|
templateUrl: './item-admin-search-result-list-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ListableObjectComponentLoaderComponent, ItemAdminSearchResultActionsComponent],
|
imports: [
|
||||||
|
ItemAdminSearchResultActionsComponent,
|
||||||
|
ListableObjectComponentLoaderComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a list element for an item search result on the admin search page
|
* The component for displaying a list element for an item search result on the admin search page
|
||||||
|
@@ -23,7 +23,11 @@ import { getItemEditRoute } from '../../../item-page/item-page-routing-paths';
|
|||||||
styleUrls: ['./item-admin-search-result-actions.component.scss'],
|
styleUrls: ['./item-admin-search-result-actions.component.scss'],
|
||||||
templateUrl: './item-admin-search-result-actions.component.html',
|
templateUrl: './item-admin-search-result-actions.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgClass, RouterLink, TranslateModule],
|
imports: [
|
||||||
|
NgClass,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying the actions for a list element for an item search result on the admin search page
|
* The component for displaying the actions for a list element for an item search result on the admin search page
|
||||||
|
@@ -10,7 +10,9 @@ import { AdminSearchPageComponent } from './admin-search-page.component';
|
|||||||
selector: 'ds-admin-search-page',
|
selector: 'ds-admin-search-page',
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminSearchPageComponent],
|
imports: [
|
||||||
|
AdminSearchPageComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ThemedAdminSearchPageComponent extends ThemedComponent<AdminSearchPageComponent> {
|
export class ThemedAdminSearchPageComponent extends ThemedComponent<AdminSearchPageComponent> {
|
||||||
|
|
||||||
|
@@ -96,7 +96,9 @@ describe('AdminSidebarSectionComponent', () => {
|
|||||||
selector: 'ds-test-cmp',
|
selector: 'ds-test-cmp',
|
||||||
template: ``,
|
template: ``,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterTestingModule],
|
imports: [
|
||||||
|
RouterTestingModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
class TestComponent {
|
class TestComponent {
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,12 @@ import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe';
|
|||||||
templateUrl: './admin-sidebar-section.component.html',
|
templateUrl: './admin-sidebar-section.component.html',
|
||||||
styleUrls: ['./admin-sidebar-section.component.scss'],
|
styleUrls: ['./admin-sidebar-section.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgClass, RouterLink, TranslateModule, BrowserOnlyPipe],
|
imports: [
|
||||||
|
BrowserOnlyPipe,
|
||||||
|
NgClass,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
|
|
||||||
})
|
})
|
||||||
export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
NgbModalRef,
|
NgbModalRef,
|
||||||
} from '@ng-bootstrap/ng-bootstrap';
|
} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
@@ -57,7 +57,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
|
|
||||||
const routeStub = {
|
const routeStub = {
|
||||||
data: observableOf({
|
data: of({
|
||||||
dso: createSuccessfulRemoteDataObject(mockItem),
|
dso: createSuccessfulRemoteDataObject(mockItem),
|
||||||
}),
|
}),
|
||||||
children: [],
|
children: [],
|
||||||
@@ -65,16 +65,16 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
const mockNgbModal = {
|
const mockNgbModal = {
|
||||||
open: jasmine.createSpy('open').and.returnValue(
|
open: jasmine.createSpy('open').and.returnValue(
|
||||||
{ componentInstance: {}, closed: observableOf({}) } as NgbModalRef,
|
{ componentInstance: {}, closed: of({}) } as NgbModalRef,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true),
|
isAuthorized: of(true),
|
||||||
});
|
});
|
||||||
scriptService = jasmine.createSpyObj('scriptService', { scriptWithNameExistsAndCanExecute: observableOf(true) });
|
scriptService = jasmine.createSpyObj('scriptService', { scriptWithNameExistsAndCanExecute: of(true) });
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule, AdminSidebarComponent],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule, AdminSidebarComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -98,10 +98,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(menuService, 'getMenuTopSections').and.returnValue(observableOf([]));
|
spyOn(menuService, 'getMenuTopSections').and.returnValue(of([]));
|
||||||
fixture = TestBed.createComponent(AdminSidebarComponent);
|
fixture = TestBed.createComponent(AdminSidebarComponent);
|
||||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||||
comp.sections = observableOf([]);
|
comp.sections = of([]);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
@@ -45,7 +45,14 @@ import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe';
|
|||||||
styleUrls: ['./admin-sidebar.component.scss'],
|
styleUrls: ['./admin-sidebar.component.scss'],
|
||||||
animations: [slideSidebar],
|
animations: [slideSidebar],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgbDropdownModule, NgClass, NgComponentOutlet, AsyncPipe, TranslateModule, BrowserOnlyPipe],
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
BrowserOnlyPipe,
|
||||||
|
NgbDropdownModule,
|
||||||
|
NgClass,
|
||||||
|
NgComponentOutlet,
|
||||||
|
TranslatePipe,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
|
@@ -8,7 +8,7 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
import { MenuService } from '../../../shared/menu/menu.service';
|
import { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { MenuItemModels } from '../../../shared/menu/menu-section.model';
|
import { MenuItemModels } from '../../../shared/menu/menu-section.model';
|
||||||
@@ -39,7 +39,7 @@ describe('ExpandableAdminSidebarSectionComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([{
|
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([{
|
||||||
id: 'test',
|
id: 'test',
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {} as MenuItemModels,
|
model: {} as MenuItemModels,
|
||||||
@@ -90,7 +90,7 @@ describe('ExpandableAdminSidebarSectionComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
|
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([]));
|
||||||
fixture = TestBed.createComponent(ExpandableAdminSidebarSectionComponent);
|
fixture = TestBed.createComponent(ExpandableAdminSidebarSectionComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
|
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
|
||||||
|
@@ -37,7 +37,13 @@ import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sid
|
|||||||
styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
|
styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
|
||||||
animations: [rotate, slide, bgColor],
|
animations: [rotate, slide, bgColor],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgClass, NgComponentOutlet, AsyncPipe, TranslateModule, BrowserOnlyPipe],
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
BrowserOnlyPipe,
|
||||||
|
NgClass,
|
||||||
|
NgComponentOutlet,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
||||||
|
@@ -15,7 +15,9 @@ import { AdminSidebarComponent } from './admin-sidebar.component';
|
|||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminSidebarComponent],
|
imports: [
|
||||||
|
AdminSidebarComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ThemedAdminSidebarComponent extends ThemedComponent<AdminSidebarComponent> {
|
export class ThemedAdminSidebarComponent extends ThemedComponent<AdminSidebarComponent> {
|
||||||
|
|
||||||
|
@@ -17,7 +17,11 @@ import {
|
|||||||
styleUrls: ['./workflow-item-admin-workflow-actions.component.scss'],
|
styleUrls: ['./workflow-item-admin-workflow-actions.component.scss'],
|
||||||
templateUrl: './workflow-item-admin-workflow-actions.component.html',
|
templateUrl: './workflow-item-admin-workflow-actions.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgClass, RouterLink, TranslateModule],
|
imports: [
|
||||||
|
NgClass,
|
||||||
|
RouterLink,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying the actions for a list element for a workflow-item on the admin workflow search page
|
* The component for displaying the actions for a list element for a workflow-item on the admin workflow search page
|
||||||
|
@@ -34,7 +34,12 @@ import { ErrorComponent } from '../../../../../../shared/error/error.component';
|
|||||||
styleUrls: ['./supervision-order-group-selector.component.scss'],
|
styleUrls: ['./supervision-order-group-selector.component.scss'],
|
||||||
templateUrl: './supervision-order-group-selector.component.html',
|
templateUrl: './supervision-order-group-selector.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [FormsModule, ErrorComponent, EpersonGroupListComponent, TranslateModule],
|
imports: [
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
ErrorComponent,
|
||||||
|
FormsModule,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class SupervisionOrderGroupSelectorComponent {
|
export class SupervisionOrderGroupSelectorComponent {
|
||||||
|
|
||||||
|
@@ -38,7 +38,12 @@ export interface SupervisionOrderListEntry {
|
|||||||
templateUrl: './supervision-order-status.component.html',
|
templateUrl: './supervision-order-status.component.html',
|
||||||
styleUrls: ['./supervision-order-status.component.scss'],
|
styleUrls: ['./supervision-order-status.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [VarDirective, NgbTooltipModule, AsyncPipe, TranslateModule],
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
NgbTooltipModule,
|
||||||
|
TranslateModule,
|
||||||
|
VarDirective,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class SupervisionOrderStatusComponent implements OnChanges {
|
export class SupervisionOrderStatusComponent implements OnChanges {
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user