Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
urgi-is
FAIDARE
Commits
b863ad68
Commit
b863ad68
authored
Apr 04, 2019
by
Jérémy Destin
Browse files
Merge branch 'fix/card_and_form_tests' into 'master'
Fix card and form tests See merge request urgi-is/gpds!20
parents
df4189c5
7027aeb3
Changes
12
Hide whitespace changes
Inline
Side-by-side
frontend/src/app/card-row/card-row.component.html
View file @
b863ad68
<ng-container
*ngIf=
"
(test && value === undefined) || (value
)"
>
<ng-container
*ngIf=
"
shouldShow(
)"
>
<div
class=
"row row-sep"
>
<div
class=
"col-4 field my-2"
>
{{ label }}
...
...
frontend/src/app/card-row/card-row.component.spec.ts
View file @
b863ad68
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
async
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
CardRowComponent
}
from
'
./card-row.component
'
;
import
{
ComponentTester
,
speculoosMatchers
}
from
'
ngx-speculoos
'
;
import
{
Component
,
ViewChild
}
from
'
@angular/core
'
;
class
CardRowComponentTester
extends
ComponentTester
<
CardRowComponent
>
{
constructor
()
{
super
(
CardRowComponent
);
}
get
rowDiv
()
{
return
this
.
element
(
'
div.row
'
);
}
get
labelDiv
()
{
return
this
.
element
(
'
div.field
'
);
}
get
valueDiv
()
{
return
this
.
element
(
'
div.col
'
);
}
}
/**
* Test gpds-card-row with a provided `ng-template`
*/
@
Component
({
selector
:
'
gpds-test
'
,
template
:
`
<gpds-card-row>
<ng-template>
<strong>Value HTML template</strong>
</ng-template>
</gpds-card-row>`
})
class
CardRowWithTemplateComponent
{
@
ViewChild
(
CardRowComponent
)
component
:
CardRowComponent
;
}
describe
(
'
CardRowComponent
'
,
()
=>
{
let
component
:
CardRowComponent
;
let
fixture
:
ComponentFixture
<
CardRowComponent
>
;
beforeEach
(()
=>
jasmine
.
addMatchers
(
speculoosMatchers
));
beforeEach
(
async
(()
=>
{
beforeEach
(
async
(()
=>
TestBed
.
configureTestingModule
({
declarations
:
[
CardRowComponent
]
})
.
compileComponents
();
}));
declarations
:
[
CardRowComponent
,
CardRowWithTemplateComponent
]
}).
compileComponents
()
));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
CardRowComponent
);
component
=
fixture
.
componentInstance
;
fixture
.
detectChanges
();
it
(
'
should hide falsy value
'
,
()
=>
{
const
tester
=
new
CardRowComponentTester
();
tester
.
componentInstance
.
label
=
'
Label1
'
;
tester
.
componentInstance
.
value
=
null
;
tester
.
detectChanges
();
expect
(
tester
.
rowDiv
).
toBeFalsy
();
});
it
(
'
should show truthy value
'
,
()
=>
{
const
tester
=
new
CardRowComponentTester
();
tester
.
componentInstance
.
label
=
'
Label1
'
;
tester
.
componentInstance
.
value
=
'
Value1
'
;
tester
.
detectChanges
();
expect
(
tester
.
rowDiv
).
toBeTruthy
();
expect
(
tester
.
labelDiv
).
toContainText
(
tester
.
componentInstance
.
label
);
expect
(
tester
.
valueDiv
).
toContainText
(
tester
.
componentInstance
.
value
);
});
it
(
'
should hide falsy test
'
,
()
=>
{
const
tester
=
new
CardRowComponentTester
();
tester
.
componentInstance
.
label
=
'
Label1
'
;
tester
.
componentInstance
.
value
=
'
Value1
'
;
tester
.
componentInstance
.
test
=
false
;
tester
.
detectChanges
();
expect
(
tester
.
rowDiv
).
toBeFalsy
();
});
it
(
'
should hide truthy test, falsy value
'
,
()
=>
{
const
tester
=
new
CardRowComponentTester
();
tester
.
componentInstance
.
label
=
'
Label1
'
;
tester
.
componentInstance
.
value
=
''
;
tester
.
componentInstance
.
test
=
true
;
tester
.
detectChanges
();
expect
(
tester
.
rowDiv
).
toBeFalsy
();
});
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
it
(
'
should show truthy test, truthy value
'
,
()
=>
{
const
tester
=
new
CardRowComponentTester
();
tester
.
componentInstance
.
label
=
'
Label1
'
;
tester
.
componentInstance
.
value
=
'
Value1
'
;
tester
.
componentInstance
.
test
=
true
;
tester
.
detectChanges
();
expect
(
tester
.
rowDiv
).
toBeTruthy
();
expect
(
tester
.
labelDiv
).
toContainText
(
tester
.
componentInstance
.
label
);
expect
(
tester
.
valueDiv
).
toContainText
(
tester
.
componentInstance
.
value
);
});
it
(
'
should hide falsy test, provided template
'
,
async
(()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardRowWithTemplateComponent
);
fixture
.
componentInstance
.
component
.
label
=
'
Label1
'
;
fixture
.
componentInstance
.
component
.
test
=
''
;
fixture
.
detectChanges
();
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
expect
(
element
.
querySelector
(
'
div.row
'
)).
toBeFalsy
();
}));
it
(
'
should show truthy test, provided template
'
,
async
(()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardRowWithTemplateComponent
);
const
component
=
fixture
.
componentInstance
.
component
;
component
.
label
=
'
Label2
'
;
component
.
test
=
true
;
fixture
.
detectChanges
();
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
expect
(
element
.
querySelector
(
'
div.row
'
)).
toBeTruthy
();
expect
(
element
.
querySelector
(
'
div.field
'
).
textContent
).
toContain
(
component
.
label
);
expect
(
element
.
querySelector
(
'
div.col
'
).
textContent
).
toContain
(
'
Value HTML template
'
);
}));
});
frontend/src/app/card-row/card-row.component.ts
View file @
b863ad68
...
...
@@ -13,4 +13,13 @@ export class CardRowComponent {
@
ContentChild
(
TemplateRef
)
template
:
TemplateRef
<
any
>
;
shouldShow
():
boolean
{
return
this
.
test
&&
(
// Value not provided and template provided
(
this
.
value
===
undefined
&&
this
.
template
!==
undefined
)
||
// Or value truthy
!!
this
.
value
);
}
}
frontend/src/app/card-section/card-section.component.spec.ts
View file @
b863ad68
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
async
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
CardSectionComponent
}
from
'
./card-section.component
'
;
import
{
speculoosMatchers
}
from
'
ngx-speculoos
'
;
import
{
Component
,
ViewChild
}
from
'
@angular/core
'
;
/**
* Test gpds-card-section with a provided `ng-template`
*/
@
Component
({
selector
:
'
gpds-test
'
,
template
:
`
<gpds-card-section>
<ng-template>
<div class="test-body">Body HTML template</div>
</ng-template>
</gpds-card-section>`
})
class
CardSectionTestWrapperComponent
{
@
ViewChild
(
CardSectionComponent
)
component
:
CardSectionComponent
;
}
describe
(
'
CardSectionComponent
'
,
()
=>
{
let
component
:
CardSectionComponent
;
let
fixture
:
ComponentFixture
<
CardSectionComponent
>
;
beforeEach
(()
=>
jasmine
.
addMatchers
(
speculoosMatchers
));
beforeEach
(
async
(()
=>
{
beforeEach
(
async
(()
=>
TestBed
.
configureTestingModule
({
declarations
:
[
CardSectionComponent
]
})
.
compileComponents
();
declarations
:
[
CardSectionComponent
,
CardSectionTestWrapperComponent
]
}).
compileComponents
()
));
it
(
'
should hide falsy test
'
,
async
(()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardSectionTestWrapperComponent
);
const
component
=
fixture
.
componentInstance
.
component
;
component
.
header
=
'
Header1
'
;
component
.
test
=
''
;
fixture
.
detectChanges
();
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
const
cardDiv
=
element
.
querySelector
(
'
div.card
'
);
expect
(
cardDiv
).
toBeFalsy
();
}));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
CardSectionComponent
);
component
=
fixture
.
componentInstance
;
it
(
'
should show truthy test
'
,
async
(()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardSectionTestWrapperComponent
);
const
component
=
fixture
.
componentInstance
.
component
;
component
.
header
=
'
Header2
'
;
component
.
test
=
true
;
fixture
.
detectChanges
();
});
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
});
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
const
cardDiv
=
element
.
querySelector
(
'
div.card
'
);
const
headerDiv
=
element
.
querySelector
(
'
div.card-header
'
);
const
bodyDiv
=
element
.
querySelector
(
'
div.test-body
'
);
expect
(
cardDiv
).
toBeTruthy
();
expect
(
headerDiv
.
textContent
).
toContain
(
component
.
header
);
expect
(
bodyDiv
.
textContent
).
toContain
(
'
Body HTML template
'
);
}));
});
frontend/src/app/card-table/card-table.component.spec.ts
View file @
b863ad68
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
async
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
CardTableComponent
}
from
'
./card-table.component
'
;
import
{
Component
,
ViewChild
}
from
'
@angular/core
'
;
/**
* Test gpds-card-table with a simple provided row `ng-template`
*/
@
Component
({
selector
:
'
gpds-test
'
,
template
:
`
<gpds-card-table>
<ng-template let-row>
<tr>
<td>{{ row[0] }}</td>
<td>{{ row[1] }}</td>
<td>{{ row[2] }}</td>
</tr>
</ng-template>
</gpds-card-table>`
})
class
CardTableTestWrapperComponent
{
@
ViewChild
(
CardTableComponent
)
component
:
CardTableComponent
;
}
describe
(
'
CardTableComponent
'
,
()
=>
{
let
component
:
CardTableComponent
;
let
fixture
:
ComponentFixture
<
CardTableComponent
>
;
beforeEach
(
async
(()
=>
{
beforeEach
(
async
(()
=>
TestBed
.
configureTestingModule
({
declarations
:
[
CardTableComponent
]
})
.
compileComponents
();
}));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
CardTableComponent
);
component
=
fixture
.
componentInstance
;
declarations
:
[
CardTableComponent
,
CardTableTestWrapperComponent
]
}).
compileComponents
()
));
const
headers
=
[
'
h1
'
,
'
h2
'
,
'
h3
'
];
const
rows
=
[
[
'
a
'
,
'
b
'
,
'
c
'
],
[
'
d
'
,
'
e
'
,
'
f
'
],
[
'
g
'
,
'
h
'
,
'
i
'
],
];
it
(
'
should hide headers and show rows
'
,
()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardTableTestWrapperComponent
);
const
component
=
fixture
.
componentInstance
.
component
;
component
.
headers
=
null
;
component
.
rows
=
rows
;
fixture
.
detectChanges
();
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
const
thead
=
element
.
querySelector
(
'
thead
'
);
expect
(
thead
).
toBeFalsy
();
const
tds
=
element
.
querySelectorAll
(
'
td
'
);
expect
(
tds
.
length
).
toBe
(
9
);
expect
(
tds
[
0
].
textContent
).
toContain
(
rows
[
0
][
0
]);
expect
(
tds
[
5
].
textContent
).
toContain
(
rows
[
1
][
2
]);
});
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
it
(
'
should show headers and rows
'
,
()
=>
{
const
fixture
=
TestBed
.
createComponent
(
CardTableTestWrapperComponent
);
const
component
=
fixture
.
componentInstance
.
component
;
component
.
headers
=
headers
;
component
.
rows
=
rows
;
fixture
.
detectChanges
();
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
const
ths
=
element
.
querySelectorAll
(
'
thead th
'
);
expect
(
ths
.
length
).
toBe
(
3
);
expect
(
ths
[
0
].
textContent
).
toContain
(
headers
[
0
]);
expect
(
ths
[
2
].
textContent
).
toContain
(
headers
[
2
]);
const
tds
=
element
.
querySelectorAll
(
'
td
'
);
expect
(
tds
.
length
).
toBe
(
9
);
expect
(
tds
[
0
].
textContent
).
toContain
(
rows
[
0
][
0
]);
expect
(
tds
[
5
].
textContent
).
toContain
(
rows
[
1
][
2
]);
});
});
frontend/src/app/form/form.component.html
View file @
b863ad68
<ul
class=
"nav nav-tabs"
>
<li
class=
"nav-item"
>
<a
tabindex=
"0"
class=
"nav-link
{{ activeTab == 'Germplasm' ? 'active' : ''
}}"
(click)=
"activeTab=
'Germplasm'
"
>
Germplasm
class=
"nav-link
germplasm {{ getNavClass(tabs.GERMPLASM)
}}"
(click)=
"activeTab=
tabs.GERMPLASM
"
>
{{ tabs.GERMPLASM }}
</a>
</li>
<li
class=
"nav-item"
>
<a
tabindex=
"1"
class=
"nav-link
{{ activeTab == 'Variable' ? 'active' : ''
}}"
(click)=
"activeTab=
'Variable'
"
>
Trait
class=
"nav-link
trait {{ getNavClass(tabs.TRAIT)
}}"
(click)=
"activeTab=
tabs.TRAIT
"
>
{{ tabs.TRAIT }}
</a>
</li>
</ul>
<!-- Germplasm tab -->
<div
class=
"
{{ activeTab == 'Germplasm' ? 'visible' : 'd-none'
}}"
>
<div
class=
"
germplasm {{ getTabClass(tabs.GERMPLASM)
}}"
>
<!-- Input for the crops field -->
<div
class=
"form-group row pt-3"
>
<label
for=
"crops"
class=
"col-sm-4"
>
...
...
@@ -70,8 +70,8 @@
</div>
</div>
<!--
Variable
tab -->
<div
class=
"
{{ activeTab == 'Variable' ? 'visible' : 'd-none'
}}"
>
<!--
Trait
tab -->
<div
class=
"
trait {{ getTabClass(tabs.TRAIT)
}}"
>
<gpds-trait-ontology-widget
[
criteria
$
]=
"criteria$"
(initialized)=
"traitWidgetInitialized.emit($event)"
>
...
...
frontend/src/app/form/form.component.spec.ts
View file @
b863ad68
import
{
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
async
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
FormComponent
}
from
'
./form.component
'
;
import
{
Component
,
NO_ERRORS_SCHEMA
}
from
'
@angular/core
'
;
import
{
Component
,
EventEmitter
,
Input
,
Output
}
from
'
@angular/core
'
;
/**
* Mock gpds-suggestion-field
*/
@
Component
({
selector
:
'
gpds-suggestion-field
'
,
template
:
'
<br/>
'
})
class
MockFieldComponent
{
class
MockSuggestionFieldComponent
{
@
Input
()
criteria$
:
any
;
}
/**
* Mock gpds-trait-ontology-widget
*/
@
Component
({
selector
:
'
gpds-trait-ontology-widget
'
,
template
:
'
<br/>
'
})
class
MockTraitWidgetComponent
{
@
Input
()
criteria$
:
any
;
@
Output
()
initialized
=
new
EventEmitter
();
}
describe
(
'
FormComponent
'
,
()
=>
{
let
component
:
FormComponent
;
let
fixture
:
ComponentFixture
<
FormComponent
>
;
beforeEach
(()
=>
{
beforeEach
(
async
(
()
=>
TestBed
.
configureTestingModule
({
declarations
:
[
FormComponent
,
Mock
Field
Component
],
schemas
:
[
NO_ERRORS_SCHEMA
]
}).
compileComponents
(
);
declarations
:
[
FormComponent
,
Mock
SuggestionFieldComponent
,
MockTraitWidget
Component
],
}).
compileComponents
()
)
);
fixture
=
TestBed
.
createComponent
(
FormComponent
);
co
mponent
=
fixture
.
componentInstance
;
it
(
'
should switch tabs
'
,
async
(()
=>
{
co
nst
fixture
=
TestBed
.
createComponent
(
FormComponent
)
;
fixture
.
detectChanges
();
});
it
(
'
should create
'
,
()
=>
{
const
element
:
HTMLElement
=
fixture
.
nativeElement
;
const
germplasmNav
:
HTMLElement
=
element
.
querySelector
(
'
a.germplasm
'
);
const
germplasmTab
:
HTMLElement
=
element
.
querySelector
(
'
div.germplasm
'
);
const
traitNav
:
HTMLElement
=
element
.
querySelector
(
'
a.trait
'
);
const
traitTab
:
HTMLElement
=
element
.
querySelector
(
'
div.trait
'
);
// Check default tab is active
expect
(
germplasmNav
.
getAttribute
(
'
class
'
)).
toContain
(
'
active
'
);
expect
(
germplasmTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
visible
'
);
expect
(
traitNav
.
getAttribute
(
'
class
'
)).
not
.
toContain
(
'
active
'
);
expect
(
traitTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
d-none
'
);
traitNav
.
click
();
fixture
.
detectChanges
();
// Check tab switched
expect
(
traitNav
.
getAttribute
(
'
class
'
)).
toContain
(
'
active
'
);
expect
(
traitTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
visible
'
);
expect
(
germplasmNav
.
getAttribute
(
'
class
'
)).
not
.
toContain
(
'
active
'
);
expect
(
germplasmTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
d-none
'
);
germplasmNav
.
click
();
fixture
.
detectChanges
();
expect
(
component
).
toBeTruthy
();
});
// Check tab switched back
expect
(
germplasmNav
.
getAttribute
(
'
class
'
)).
toContain
(
'
active
'
);
expect
(
germplasmTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
visible
'
);
expect
(
traitNav
.
getAttribute
(
'
class
'
)).
not
.
toContain
(
'
active
'
);
expect
(
traitTab
.
getAttribute
(
'
class
'
)).
toContain
(
'
d-none
'
);
}));
});
frontend/src/app/form/form.component.ts
View file @
b863ad68
...
...
@@ -2,6 +2,11 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import
{
DataDiscoveryCriteria
}
from
'
../models/data-discovery.model
'
;
import
{
BehaviorSubject
}
from
'
rxjs
'
;
enum
Tabs
{
GERMPLASM
=
'
Germplasm
'
,
TRAIT
=
'
Trait
'
}
@
Component
({
selector
:
'
gpds-form
'
,
templateUrl
:
'
./form.component.html
'
,
...
...
@@ -10,5 +15,18 @@ import { BehaviorSubject } from 'rxjs';
export
class
FormComponent
{
@
Input
()
criteria$
:
BehaviorSubject
<
DataDiscoveryCriteria
>
;
@
Output
()
traitWidgetInitialized
=
new
EventEmitter
();
activeTab
=
'
Germplasm
'
;
// Default active tab
activeTab
:
Tabs
=
Tabs
.
GERMPLASM
;
// to give access in HTML template
tabs
=
Tabs
;
getNavClass
(
tab
:
Tabs
)
{
return
this
.
activeTab
===
tab
?
'
active
'
:
''
;
}
getTabClass
(
tab
:
Tabs
)
{
return
this
.
activeTab
===
tab
?
'
visible
'
:
'
d-none
'
;
}
}
frontend/src/app/site-card/site-card.component.ts
View file @
b863ad68
...
...
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import
{
BrapiService
}
from
'
../brapi.service
'
;
import
{
ActivatedRoute
}
from
'
@angular/router
'
;
import
{
BrapiLocation
}
from
'
../models/brapi.model
'
;
import
{
KeyValueObject
}
from
'
../utils
'
;
import
{
KeyValueObject
,
toKeyValueObjects
}
from
'
../utils
'
;
import
{
DataDiscoverySource
}
from
'
../models/data-discovery.model
'
;
import
{
GnpisService
}
from
'
../gnpis.service
'
;
...
...
@@ -30,7 +30,7 @@ export class SiteCardComponent implements OnInit {
this
.
location
=
response
.
result
;
this
.
additionalInfos
=
[];
if
(
this
.
location
.
additionalInfo
)
{
this
.
manageAdditionalInfo
(
KeyValueObject
.
fromObject
(
this
.
location
.
additionalInfo
).
sort
());
this
.
manageAdditionalInfo
(
to
KeyValueObject
s
(
this
.
location
.
additionalInfo
).
sort
());
}
const
sourceURI
=
location
[
'
schema:includedInDataCatalog
'
];
// TODO Remove the condition when the field includedInDataCatalog will be added to URGI study.
...
...
frontend/src/app/study-card/study-card.component.ts
View file @
b863ad68
...
...
@@ -5,7 +5,7 @@ import { BrapiGermplasm, BrapiObservationVariable, BrapiStudy, BrapiTrial } from
import
{
GnpisService
}
from
'
../gnpis.service
'
;
import
{
DataDiscoverySource
}
from
'
../models/data-discovery.model
'
;
import
{
KeyValueObject
}
from
'
../utils
'
;
import
{
KeyValueObject
,
toKeyValueObjects
}
from
'
../utils
'
;
@
Component
({
selector
:
'
gpds-study-card
'
,
...
...
@@ -43,7 +43,7 @@ export class StudyCardComponent implements OnInit {
this
.
additionalInfos
=
[];
if
(
this
.
study
.
additionalInfo
)
{
this
.
additionalInfos
=
KeyValueObject
.
fromObject
(
this
.
study
.
additionalInfo
).
sort
();
this
.
additionalInfos
=
to
KeyValueObject
s
(
this
.
study
.
additionalInfo
).
sort
();
}
// Get study trials
...
...
frontend/src/app/utils.spec.ts
View file @
b863ad68
import
{
KeyValueObject
}
from
'
./utils
'
;
import
{
KeyValueObject
,
toKeyValueObjects
}
from
'
./utils
'
;
describe
(
'
KeyValueObject
'
,
()
=>
{
it
(
'
should convert JS object to array of KeyValueObject
'
,
()
=>
{
const
actual
=
KeyValueObject
.
fromObject
({
const
actual
=
to
KeyValueObject
s
({
'
a
'
:
'
1
'
,
'
b
'
:
'
2
'
,
'
c
'
:
null
,
...
...
@@ -13,10 +13,10 @@ describe('KeyValueObject', () => {
'
f
'
:
'
3
'
});
const
expected
=
[
new
KeyValueObject
(
'
a
'
,
'
1
'
)
,
new
KeyValueObject
(
'
b
'
,
'
2
'
)
,
new
KeyValueObject
(
'
f
'
,
'
3
'
)
,
const
expected
:
KeyValueObject
[]
=
[
{
key
:
'
a
'
,
value
:
'
1
'
}
,
{
key
:
'
b
'
,
value
:
'
2
'
}
,
{
key
:
'
f
'
,
value
:
'
3
'
}
,
];
expect
(
actual
).
toEqual
(
expected
);
...
...
frontend/src/app/utils.ts
View file @
b863ad68
...
...
@@ -8,19 +8,18 @@ export function asArray<T>(obj: T | T[]): T[] {
return
[
obj
];
}
export
class
KeyValueObject
{
public
key
:
string
;
public
value
:
string
;
constructor
(
key
:
string
,
value
:
string
)
{
this
.
key
=
key
;
this
.
value
=
value
;
}
export
interface
KeyValueObject
{
key
:
string
;
value
:
string
;
}
static
fromObject
(
o
:
{
[
key
:
string
]:
string
}):
KeyValueObject
[]
{
return
Object
.
entries
(
o
)
/**
* Transform an object with string keys and values to a list of `KeyValueObject`.
* Also makes sure the keys and values are truthy.