Skip to content

Commit b433d97

Browse files
committed
Add end2end test for filtering courses at the backend courses view
1 parent 8475bc4 commit b433d97

File tree

7 files changed

+96
-7
lines changed

7 files changed

+96
-7
lines changed

cypress.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"testFiles": "**/*.cypress.*",
66
"integrationFolder": "tests/apps",
77
"fixturesFolder": "tests/utils/cypress/fixtures",
8-
"pluginsFile": "tests/utils/cypress/plugins/index.ts"
8+
"pluginsFile": "tests/utils/cypress/plugins/index.ts",
9+
"experimentalStudio": true
910
}

src/Contexts/Backoffice/Courses/application/Create/BackofficeCourseCreator.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { Criteria } from '../../../../Shared/domain/criteria/Criteria';
2+
import { Filters } from '../../../../Shared/domain/criteria/Filters';
3+
import { Order } from '../../../../Shared/domain/criteria/Order';
14
import { BackofficeCourse } from '../../domain/BackofficeCourse';
5+
import { BackofficeCourseAlreadyExists } from '../../domain/BackofficeCourseAlreadyExists';
26
import { BackofficeCourseDuration } from '../../domain/BackofficeCourseDuration';
37
import { BackofficeCourseId } from '../../domain/BackofficeCourseId';
48
import { BackofficeCourseName } from '../../domain/BackofficeCourseName';
@@ -14,6 +18,26 @@ export class BackofficeCourseCreator {
1418
new BackofficeCourseDuration(duration)
1519
);
1620

21+
const criteria = this.buildCriteria(id);
22+
const alreadyExists = await this.courseAlreadyExists(criteria);
23+
if (alreadyExists) {
24+
throw new BackofficeCourseAlreadyExists(new BackofficeCourseId(id));
25+
}
26+
1727
return this.backofficeCourseRepository.save(course);
1828
}
29+
30+
private async courseAlreadyExists(criteria: Criteria) {
31+
const current = await this.backofficeCourseRepository.matching(criteria);
32+
return current.length > 0;
33+
}
34+
35+
buildCriteria(id: string): Criteria {
36+
const filter = new Map<string, string>();
37+
filter.set('field', 'id');
38+
filter.set('operator', '=');
39+
filter.set('value', id);
40+
const filters = Filters.fromValues([filter]);
41+
return new Criteria(filters, Order.none());
42+
}
1943
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { BackofficeCourseId } from './BackofficeCourseId';
2+
3+
export class BackofficeCourseAlreadyExists extends Error {
4+
constructor(id: BackofficeCourseId) {
5+
super(`Course with id ${id} already exists`);
6+
}
7+
}

src/Contexts/Shared/domain/criteria/FilterOperator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,8 @@ export class FilterOperator extends EnumValueObject<Operator> {
4141
protected throwErrorForInvalidValue(value: Operator): void {
4242
throw new InvalidArgumentError(`The filter operator ${value} is invalid`);
4343
}
44+
45+
static equal() {
46+
return this.fromValue(Operator.EQUAL);
47+
}
4448
}

src/apps/backoffice/frontend/templates/pages/courses/partials/list_courses.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ <h3 class="font-sans text-gray-800 text-center text-3xl mb-10">Cursos existentes
88
<div class="mt-10 inline-block relative w-2/4">
99
<button class="md:w-1/4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
1010
id="add-field-button"
11+
data-cy="add-field-button"
1112
onclick="addFilter(event)">
1213
Añadir filtro
1314
</button>
@@ -18,7 +19,7 @@ <h3 class="font-sans text-gray-800 text-center text-3xl mb-10">Cursos existentes
1819
👉 Filtrar 👈
1920
</button>
2021
</div>
21-
</form>
22+
</form>
2223

2324
<table class="text-left w-full border-collapse">
2425
<thead>
@@ -82,7 +83,7 @@ <h3 class="font-sans text-gray-800 text-center text-3xl mb-10">Cursos existentes
8283
" <label class=\"block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2\" for=\"field\">\n" +
8384
" Campo\n" +
8485
" </label>\n" +
85-
" <select name=\"filters[" +
86+
" <select data-cy=\"field-filter\" name=\"filters[" +
8687
totalRows +
8788
"][field]\" id=\"field\"\n" +
8889
" class=\"block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline\">\n" +
@@ -100,7 +101,7 @@ <h3 class="font-sans text-gray-800 text-center text-3xl mb-10">Cursos existentes
100101
" <label class=\"block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2\" for=\"operator\">\n" +
101102
" Operador\n" +
102103
" </label>\n" +
103-
" <select id=\"operator\" name=\"filters[" +
104+
" <select data-cy=\"field-filter-type\" id=\"operator\" name=\"filters[" +
104105
totalRows +
105106
"][operator]\"\n" +
106107
" class=\"block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline\">\n" +
@@ -120,7 +121,7 @@ <h3 class="font-sans text-gray-800 text-center text-3xl mb-10">Cursos existentes
120121
" Valor\n" +
121122
" </label>\n" +
122123
" <input class=\"appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500\"\n" +
123-
" id=\"value\" type=\"text\" name=\"filters[" +
124+
" data-cy=\"filter-value\" id=\"value\" type=\"text\" name=\"filters[" +
124125
totalRows +
125126
"][value]\" placeholder=\"Lo que sea...\">\n" +
126127
" </div>\n" +

tests/Contexts/Backoffice/Courses/application/Create/BackofficeCourseCreator.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BackofficeCourseCreator } from '../../../../../../src/Contexts/Backoffice/Courses/application/Create/BackofficeCourseCreator';
2-
import { BackofficeCourseRepositoryMock } from '../../__mocks__/BackofficeCourseRepositoryMock';
2+
import { BackofficeCourseAlreadyExists } from '../../../../../../src/Contexts/Backoffice/Courses/domain/BackofficeCourseAlreadyExists';
33
import { BackofficeCourseMother } from '../../domain/BackofficeCourseMother';
4+
import { BackofficeCourseRepositoryMock } from '../../__mocks__/BackofficeCourseRepositoryMock';
45

56
describe('BackofficeCourseCreator', () => {
67
it('creates a backoffice course', async () => {
@@ -13,4 +14,15 @@ describe('BackofficeCourseCreator', () => {
1314

1415
repository.assertSaveHasBeenCalledWith(course);
1516
});
17+
18+
it('throws an error if the course already exists', async () => {
19+
const course = BackofficeCourseMother.random();
20+
const repository = new BackofficeCourseRepositoryMock();
21+
repository.returnMatching([course]);
22+
const applicationService = new BackofficeCourseCreator(repository);
23+
24+
expect(
25+
applicationService.run(course.id.toString(), course.duration.toString(), course.name.toString())
26+
).rejects.toBeInstanceOf(BackofficeCourseAlreadyExists);
27+
});
1628
});

tests/apps/backoffice/frontend/features/courses/list_courses.cypress.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
describe('List courses', () => {
2-
before(() => {
2+
beforeEach(() => {
33
cy.task('reset:backoffice:db');
44
cy.task('reset:mooc:db');
55
});
@@ -25,4 +25,44 @@ describe('List courses', () => {
2525
cy.get('#courses-list tr').eq(0).should('contain', 'DDD en Typescript');
2626
cy.get('#courses-list tr').eq(0).should('contain', '25 hours');
2727
});
28+
29+
it('can filter courses by its name', () => {
30+
createCourses();
31+
32+
cy.get('button[data-cy="add-field-button"').click();
33+
cy.get('[data-cy=field-filter]').select('name');
34+
cy.get('[data-cy=field-filter-type]').select('CONTAINS');
35+
cy.get('[data-cy=filter-value]').type('Random');
36+
cy.get('#filter-button').click();
37+
38+
cy.contains('Cursos existentes');
39+
cy.get('#courses-list').find('tr').should('have.length', 1);
40+
cy.get('#courses-list tr').eq(0).should('contain', 'Random Course');
41+
cy.get('#courses-list tr').eq(0).should('contain', '2 hours');
42+
});
43+
44+
it('handle invalid filters', () => {
45+
createCourses();
46+
47+
cy.get('button[data-cy="add-field-button"').click();
48+
cy.get('[data-cy=field-filter]').select('name');
49+
cy.get('[data-cy=field-filter-type]').select('CONTAINS');
50+
cy.get('#filter-button').click();
51+
52+
cy.contains('Cursos existentes');
53+
cy.get('#courses-list').find('tr').should('have.length', 1);
54+
cy.get('#courses-list tr').eq(0).should('contain', 'Random Course');
55+
cy.get('#courses-list tr').eq(0).should('contain', '2 hours');
56+
});
2857
});
58+
function createCourses() {
59+
cy.get('input[name="name"]').type('DDD en Typescript');
60+
cy.get('input[name="duration"]').type('25 hours');
61+
cy.get('form[data-cy="create-course"]').submit();
62+
63+
cy.get('input[name="name"]').type('Random Course');
64+
cy.get('input[name="duration"]').type('2 hours');
65+
cy.get('form[data-cy="create-course"]').submit();
66+
cy.wait(1000);
67+
cy.reload();
68+
}

0 commit comments

Comments
 (0)