diff --git a/client/app/controllers/locations/get.ts b/client/app/controllers/locations/get.ts new file mode 100644 index 0000000..936a824 --- /dev/null +++ b/client/app/controllers/locations/get.ts @@ -0,0 +1,38 @@ +import Controller from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +import type Location from 'sokown-client/models/location'; +import type { Position } from 'sokown-client/types'; + +export default class LocationGetController extends Controller { + declare model: Location; + @tracked targetDate: string = _getCurrentDateAsString(); + @tracked futurePosition: Position | null = null; + + @action + setTargetDate(event: Event): void { + this.targetDate = (event.target as HTMLInputElement).value; + } + + @action + async calculateFuturePosition(event: Event): Promise { + event.preventDefault(); + + const targetLocationCode = this.model.id; + const targetTimestamp = new Date(this.targetDate).getTime(); + + const response = await fetch( + `/api/locations/${targetLocationCode}/position?targetDate=${targetTimestamp}`, + ); + const json = await response.json(); + const { x, y } = json.data.attributes; + this.futurePosition = { x, y }; + } +} + +function _getCurrentDateAsString() { + const now = new Date(); + now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); + return now.toISOString().slice(0, 16); +} diff --git a/client/app/templates/locations/get.hbs b/client/app/templates/locations/get.hbs index 87a8674..86befd6 100644 --- a/client/app/templates/locations/get.hbs +++ b/client/app/templates/locations/get.hbs @@ -8,3 +8,35 @@ + +

Calculate future position

+ +
+
+ +
+
Date
+ +
+
+ +
+ +
+
+ +{{#if this.futurePosition}} +
+
Future position
+
+ +
+
+{{/if}} diff --git a/client/mirage/config.js b/client/mirage/config.js index 3354d4f..7604b41 100644 --- a/client/mirage/config.js +++ b/client/mirage/config.js @@ -67,27 +67,15 @@ function routes() { this.get('/api/locations'); - // These comments are here to help you get started. Feel free to delete them. + this.get('/api/locations/:id'); - /* - Config (with defaults). - - Note: these only affect routes defined *after* them! - */ - - // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server - // this.namespace = ''; // make this `/api`, for example, if your API is namespaced - // this.timing = 400; // delay for each request, automatically set to 0 during testing - - /* - Shorthand cheatsheet: - - this.get('/posts'); - this.post('/posts'); - this.get('/posts/:id'); - this.put('/posts/:id'); // or this.patch - this.del('/posts/:id'); - - https://miragejs.com/docs/getting-started/overview/ - */ + this.get('/api/locations/:id/position', () => { + return { + data: { + id: 'moon', + type: 'position', + attributes: { x: 1, y: 2 }, + }, + }; + }); } diff --git a/client/tests/acceptance/locations-test.ts b/client/tests/acceptance/locations-test.ts index c75c76e..514961a 100644 --- a/client/tests/acceptance/locations-test.ts +++ b/client/tests/acceptance/locations-test.ts @@ -1,7 +1,7 @@ import { module, test } from 'qunit'; import { visit } from '@1024pix/ember-testing-library'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { click } from '@ember/test-helpers'; +import { click, fillIn } from '@ember/test-helpers'; import 'qunit-dom'; import { setupApplicationTest } from 'sokown-client/tests/helpers'; @@ -47,6 +47,28 @@ module('Acceptance | locations', function (hooks) { assert .dom(screen.getByRole('definition', { name: 'Current position' })) .hasText('1.000 2.000'); + assert + .dom(screen.queryByRole('definition', { name: 'Future position' })) + .doesNotExist(); + }); + + module('calculating future position', function () { + test('it queries and displays future position for target date', async function (assert) { + // when + const screen = await visit('/locations/moon'); + await fillIn( + screen.getByLabelText('Target date'), + '2019-04-28T02:42:00', + ); + await click(screen.getByRole('button', { name: 'Calculate' })); + + // then + assert + .dom( + await screen.findByRole('definition', { name: 'Future position' }), + ) + .hasText('1.000 2.000'); + }); }); }); }); diff --git a/client/tests/unit/controllers/location/get-test.ts b/client/tests/unit/controllers/location/get-test.ts new file mode 100644 index 0000000..d86f173 --- /dev/null +++ b/client/tests/unit/controllers/location/get-test.ts @@ -0,0 +1,52 @@ +import { module, test } from 'qunit'; +import sinon from 'sinon'; + +import { setupTest } from 'sokown-client/tests/helpers'; +import type LocationGetController from 'sokown-client/controllers/locations/get'; + +module('Unit | Controller | location/get', function (hooks) { + setupTest(hooks); + + module('setTargetDate', function () { + test('it sets target date', function (assert) { + // given + const controller = this.owner.lookup( + 'controller:locations/get', + ) as LocationGetController; + controller.set('targetDate', '2013-05-22T13:30:00'); + const event = { + target: { value: '2019-04-28T02:42:00' }, + } as unknown as Event; + + // when + controller.setTargetDate(event); + + // then + assert.strictEqual(controller.get('targetDate'), '2019-04-28T02:42:00'); + }); + }); + + module('calculateFuturePosition', function () { + test('it queries api for future position', async function (assert) { + // given + const controller = this.owner.lookup( + 'controller:locations/get', + ) as LocationGetController; + controller.set('model', { id: 'moon' }); + controller.set('targetDate', '2013-05-22T21:30:00'); + const event = new Event('submit'); + + const fetchStub = sinon.stub(window, 'fetch'); + const body = JSON.stringify({ data: { attributes: { x: 1, y: 2 } } }); + const response = new Response(body); + fetchStub.resolves(response); + + // when + await controller.calculateFuturePosition(event); + + // then + assert.ok(fetchStub.calledOnce); + assert.deepEqual(controller.get('futurePosition'), { x: 1, y: 2 }); + }); + }); +});