diff --git a/config.json b/config.json index d054b5c0c8..e332b78307 100644 --- a/config.json +++ b/config.json @@ -404,6 +404,21 @@ "template-strings" ], "status": "beta" + }, + { + "slug": "appointment-time", + "name": "Appointment Time", + "uuid": "0f694053-a388-457f-89ca-f49be4560469", + "concepts": [ + "dates" + ], + "prerequisites": [ + "classes", + "objects", + "conditionals", + "type-conversion" + ], + "status": "beta" } ], "practice": [ diff --git a/exercises/concept/appointment-time/.docs/hints.md b/exercises/concept/appointment-time/.docs/hints.md new file mode 100644 index 0000000000..805e162bdd --- /dev/null +++ b/exercises/concept/appointment-time/.docs/hints.md @@ -0,0 +1,29 @@ +# Hints + +## 1. Create an appointment + +- You need to create a new date. The introduction elaborates on the different ways. +- `Date.now()` gives you current time in milliseconds +- A day consist of 24 hour. An hour consist of 60 minutes. A minute consist of 60 seconds. A second consist of 1000 milliseconds. In order to get timestamp of `n` days later from current date, you can sum current timestamp and `n * 24 * 60 * 60 * 1000`. + +## 2. Convert a date into a timestamp + +- The introduction lists the various ways how to convert a date to different types. Can you use one of those methods? + +## 3. Get the details of an appointment + +- The introduction has all the required information to extract the information from a date. + +## 4. Update an appointment with the given options + +- The introduction has all the required information to extract the information from a date. +- You can reuse `getAppointmentDetails` + +## 5. Get available times between two appointments + +- General subtraction between two dates will give you the timestamp between the two dates. +- You probably want to convert to a number first, and not rely on type-coercion. + +## 6. Check if an appointment is now valid or not + +- Conditional operators will help you to decide which date is bigger or smaller between two dates. diff --git a/exercises/concept/appointment-time/.docs/instructions.md b/exercises/concept/appointment-time/.docs/instructions.md new file mode 100644 index 0000000000..cc198e9c35 --- /dev/null +++ b/exercises/concept/appointment-time/.docs/instructions.md @@ -0,0 +1,75 @@ +# Instructions + +In this exercise you will work on some functions in order to manage appointments. +The system stores everything in ISO 8601 formatted strings, but that's not how people use the calendar. +Various functions are necessary to convert between the various formats. + +## 1. Create an appointment + +Create an appointment `n` days from now at current time. +The function takes `n` days and return the appointment time of `n` days from now. + +```javascript +createAppointment(4, now); +// Given now is Sun Oct 05 2022 23:28:43 GMT+0600 (Bangladesh Standard Time) +// => Sun Oct 09 2022 23:28:43 GMT+0600 (Bangladesh Standard Time) +``` + +If the second parameter `now` is unused, the current time should be used instead. + +## 2. Convert a date into a timestamp + +Various tools only work with the internationally standardized ISO 8601 format. +Write the function `getAppointmentTimestamp` to take a date and return a string in that format. + +```javascript +const appointment = new Date(Date.UTC(2010, 6, 16, 12, 42, 0, 0)); + +getAppointmentTimestamp(appointment); +// => '2010-07-16T12:42:00.000Z' +``` + +## 3. Get the details of an appointment + +Timestamps are hard to read; a function to get the appointment details should help with that. +The function `getAppointmentDetails` takes a timestamp in the ISO 8601 format, and returns the year, month, date, hour, and minute. + +```javascript +getAppointmentDetails('2022-04-24T08:15:00.000'); +// => { year: 2022, month: 3, date: 24, hour: 8, minute: 15 } +``` + +## 4. Update an appointment with the given options + +The function will receive first argument as appointment time and second argument of object of some options. +You have to update the appointment according to the options in the object and return the new appointment date. +The options object could have multiple options. + +```javascript +updateAppointment('2022-02-09T09:20:00.000', { month: 6 }); +// => { year: 2022, month: 6, date: 9, hour: 10, minute: 20 } +``` + +## 5. Get the available time between two appointments + +The function will receive two appointments (timestamps) as arguments. +You have to return the difference between those two times in seconds. + +Because half a second is almost meaningless, round the number before returning it. + +```javascript +timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000'); +// => 515400 +``` + +## 6. Check if an appointment is now valid or not + +Finally, when the appointment is made, the system needs to check if it's valid. +In other words, the appointment must be in the future, and not the past. + +Write the function `isValid` which takes two arguments, an appointment timestamp (string), and the current time as a timestamp (string) and returns `true` if the appointment is in the future, given the current time. + +```javascript +isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000'); +// => true +``` diff --git a/exercises/concept/appointment-time/.docs/introduction.md b/exercises/concept/appointment-time/.docs/introduction.md new file mode 100644 index 0000000000..2109d68510 --- /dev/null +++ b/exercises/concept/appointment-time/.docs/introduction.md @@ -0,0 +1,179 @@ +# Introduction + +JavaScript has a built-in object `Date` which stores date and time, and provides methods for their management. + + +~~~exercism/caution +It was based on Java's `java.util.Date` class, which was replaced in the early 2010s, but for backwards compatibility, JavaScript's `Date` sticks around. + +Because of how hard it is to work with Dates in general and because of how bad or non-existing timezone handling is, many libraries exist such as `moment.js`, `day.js`, `date-fns` and `luxon`. +None of these are available on Exercism. + +In your own projects, do not use a deprecated / unmaintained package such as `moment.js` but rely on more modern alternatives like `luxon`, or the not yet widely available [Temporal][mdn-temporal]. +This exercise focusses on `Date`, which will remain relevant until the end of JavaScript. +~~~ + +## Creation + +A `Date` object in an instance of the `Date` class. +It can be created without passing any arguments to the constructor function. +This results in a `Date` object that represents the current date and time: + +```javascript +const now = new Date(); +// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time) +// Shows current day, date and time (in your time zone). +``` + +### Unix timestamp (number) + +If a number is passed in, this will be interpreted as a `timestamp`. +A timestamp is an integer number representing the number of **milliseconds** that has passed since **1 January 1970 [UTC][defn-utc]+0**. + +```javascript +const epoch = new Date(0); +// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time) + +const another = new Date(1749508766627); +// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time) +``` + +One may expect `new Date(0)` to generate the "earliest" date object, but JavaScript will convert the date to your local timezone, which means that only those around [GMT / with an UTC+0][defn-gmt] timezone will actually get the [Unix epoch][defn-unix-epoch] value. + +### ISO 8601 timestamp (string) + +You can pass a string value representing a date to the `Date` constructor. +The **only** format that is consistent across implementations is the [simplified version][mdn-date-string-format] of the internationally recognized and standardized so-called [ISO 8601 timestamp strings][defn-iso8601]. + +A moment in time at [UTC][defn-gmt] looks like this: + +```text +YYYY-MM-DDTHH:MM:SSZ +YYYYMMDDTHHMMSSZ +``` + +Where the following substitutions take place: + +| Key | Description | +| ---- | ------------------------------------------- | +| YYYY | The calendar year, represented in 4 digits | +| MM | The calendar month, represented in 2 digits | +| DD | The calendar day, represented in 2 digits | +| HH | The hours in a 24-hour clock, 2 digits | +| MM | The minutes, 2 digits | +| SS | The seconds, 2 digits | + +The letter `T` separates the date from the time. +The letter `Z` indicates UTC (no timezone, no Day Light Savings). + + +~~~exercism/caution +Other formats that are accepted by `Date.parse` may or may not work. +When working with Dates in JavaScript, _always_ use an ISO 8601 timestamp when converting from a `string` to a `Date`. + +Date-only forms are allowed, but not all ISO 8601 formats are supported. +Consult the [simplified version explanation page on MDN][mdn-date-string-format]. +~~~ + +If the timestamp does not end in `Z`, and it does not end with `+HH:MM` or `-HH:MM`, indicating a timezone offset, because of historical reasons, the following applies: + +> When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time. +> The interpretation as a UTC time is due to a historical spec error that was not consistent with ISO 8601 but could not be changed due to web compatibility. +> See [Broken Parser – A Web Reality Issue][ref-broken-parser]. + +### Date object + +An existing date object can also be used as a constructor argument. +This makes a copy of the existing `Date` object with the same date and time. + +```javascript +const t1 = new Date(); +const t2 = new Date(t1); +// Values of t1 and t2 will be the same. +``` + +### Supplying individual date and time component values + +A date representing a date can be created by passing three numbers. +A date representing a date and time can be created by passing in 6 numbers. + +```javascript +const date1 = new Date(95, 11, 17); +// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC. + +const date2 = new Date(2013, 12, 5, 13, 24, 0); +// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC. +``` + +The second value is the `month`, which starts at `0` for January, up to `11` for December. + +## `Date.parse()` + +You may find mentions of or references to a date parsing function `Date.parse`. +Because there are only a few invariants (truths) for this function and because the rest of the implementation is not specified (and thus not standard), one should not use it. + +## Accessing `Date` components + +There are various methods on date objects that return the components of the date: + +```javascript +getFullYear(); // Get the year (4 digits) +getMonth(); // Get the month, from 0 to 11. +getDate(); // Get the day of month, from 1 to 31. +getHours(); // Get the hour in a 24 clock, from 0 to 23 +getMinutes(); // Get the minutes, from 0 to 59 +getSeconds(); // Get the seconds, from 0 to 59 +getMilliseconds(); // Get the milliseconds, from 0 and 999 +getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday). +``` + +Each of these has an applicable `set` variant (e.g. `setFullYear`) to update the value. +Any overflowing value rolls over to the next component: + +```javascript +const date = new Date('2025-02-28T12:42:00Z'); +// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time) + +date.setDate(29); +// there was no February 29th in 2025. + +date.getDate(); +// => 1 + +date.toString(); +// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time) +``` + +There are UTC variants for all the methods that disregard the local timezone. + +## Converting from date + +Date objects have a method `getTime()` that returns the UNIX timestamp in milliseconds, ie. amount of milliseconds the midnight at the beginning of January 1, 1970, UTC. +Additionally, a method `toISOString()` is available to convert from a date object to a ISO 8601 timestamp string. + +## Comparing Dates + +Greater than (`>`) and greater than or equals (`>=`) as well as less than (`<`) and less than or equals (`<=`) can be used directly between two dates or a date and a number. +This works because JavaScript will try to coerce the date to a primitive. + + +~~~@exercism/advanced +When doing a comparison between two dates or date and a number, JavaScript calls [`[Symbol.toPrimitive]("number")`][mdn-to-primitive] which internally calls [`date.valueOf()`][mdn-date-value-of]. +The latter is the same as calling [`date.getTime()`][mdn-date-get-time]. + +If you do not want to rely on this behaviour, convert to a number using `getTime()` first. +~~~ + +Dates cannot be compared using equality (`==`, and `===`), but the result of `.getTime()` can. + +[defn-utc]: https://simple.wikipedia.org/wiki/Coordinated_Universal_Time +[defn-gmt]: https://simple.wikipedia.org/wiki/Greenwich_Mean_Time +[defn-unix-epoch]: https://en.wikipedia.org/wiki/Epoch_%28computing%29 +[defn-iso8601]: https://en.wikipedia.org/wiki/ISO_8601 +[mdn-temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal +[mdn-diff-assumed-timezone]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#differences_in_assumed_time_zone +[mdn-date-string-format]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format +[mdn-to-primitive]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Symbol.toPrimitive +[mdn-date-value-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf +[mdn-date-get-time]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime +[ref-broken-parser]: https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/ diff --git a/exercises/concept/appointment-time/.gitignore b/exercises/concept/appointment-time/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/appointment-time/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/appointment-time/.meta/config.json b/exercises/concept/appointment-time/.meta/config.json new file mode 100644 index 0000000000..4488a8e345 --- /dev/null +++ b/exercises/concept/appointment-time/.meta/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "SalahuddinAhammed", + "SleeplessByte" + ], + "files": { + "solution": [ + "appointment-time.js" + ], + "test": [ + "appointment-time.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Learn how to work with dates by managing appointments", + "custom": { + "version.tests.compatibility": "jest-29", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/concept/appointment-time/.meta/exemplar.js b/exercises/concept/appointment-time/.meta/exemplar.js new file mode 100644 index 0000000000..ae0b84fca7 --- /dev/null +++ b/exercises/concept/appointment-time/.meta/exemplar.js @@ -0,0 +1,87 @@ +// @ts-check + +/** + * Create an appointment + * + * @param {number} days + * @param {number} [now] (ms since the epoch, or undefined) + * + * @returns {Date} the appointment + */ +export function createAppointment(days, now = Date.now()) { + return new Date(now + days * 24 * 3600 * 1000); +} + +/** + * Generate the appointment timestamp + * + * @param {Date} appointmentDate + * + * @returns {string} timestamp + */ +export function getAppointmentTimestamp(appointmentDate) { + return appointmentDate.toISOString(); +} + +/** + * Get details of an appointment + * + * @param {string} timestamp (ISO 8601) + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function getAppointmentDetails(timestamp) { + const appointmentDate = new Date(timestamp); + + return { + year: appointmentDate.getFullYear(), + month: appointmentDate.getMonth(), + date: appointmentDate.getDate(), + hour: appointmentDate.getHours(), + minute: appointmentDate.getMinutes(), + }; +} + +/** + * Update an appointment with given options + * + * @param {string} timestamp (ISO8601) + * @param {Partial>} options + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function updateAppointment(timestamp, options) { + let appointmentDate = new Date(timestamp); + + if (options.year !== undefined) appointmentDate.setFullYear(options.year); + if (options.month !== undefined) appointmentDate.setMonth(options.month); + if (options.date !== undefined) appointmentDate.setDate(options.date); + if (options.hour !== undefined) appointmentDate.setHours(options.hour); + if (options.minute !== undefined) appointmentDate.setMinutes(options.minute); + + return getAppointmentDetails(appointmentDate.toISOString()); +} + +/** + * Get available time in seconds (rounded) between two appointments + * + * @param {string} timestampA (ISO 8601) + * @param {string} timestampB (ISO 8601) + * + * @returns {number} amount of seconds (rounded) + */ +export function timeBetween(timestampA, timestampB) { + return Math.round( + (new Date(timestampB).getTime() - new Date(timestampA).getTime()) / 1000, + ); +} + +/** + * Checks if the appointment is in the past + * + * @param {string} appointmentTimestamp (ISO 8601) + * @param {string} currentTimestamp (ISO 8601) + */ +export function isValid(appointmentTimestamp, currentTimestamp) { + return new Date(appointmentTimestamp) > new Date(currentTimestamp); +} diff --git a/exercises/concept/appointment-time/.npmrc b/exercises/concept/appointment-time/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/appointment-time/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/appointment-time/LICENSE b/exercises/concept/appointment-time/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/appointment-time/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/appointment-time/appointment-time.js b/exercises/concept/appointment-time/appointment-time.js new file mode 100644 index 0000000000..9b0b28ce0a --- /dev/null +++ b/exercises/concept/appointment-time/appointment-time.js @@ -0,0 +1,69 @@ +// @ts-check + +/** + * Create an appointment + * + * @param {number} days + * @param {number} [now] (ms since the epoch, or undefined) + * + * @returns {Date} the appointment + */ +export function createAppointment(days, now = Date.now()) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Generate the appointment timestamp + * + * @param {Date} appointmentDate + * + * @returns {string} timestamp + */ +export function getAppointmentTimestamp(appointmentDate) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get details of an appointment + * + * @param {string} timestamp (ISO 8601) + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function getAppointmentDetails(timestamp) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Update an appointment with given options + * + * @param {string} timestamp (ISO 8601) + * @param {Partial>} options + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function updateAppointment(timestamp, options) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get available time in seconds (rounded) between two appointments + * + * @param {string} timestampA (ISO 8601) + * @param {string} timestampB (ISO 8601) + * + * @returns {number} amount of seconds (rounded) + */ +export function timeBetween(timestampA, timestampB) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get available times between two appointment + * + * @param {string} appointmentTimestamp (ISO 8601) + * @param {string} currentTimestamp (ISO 8601) + */ +export function isValid(appointmentTimestamp, currentTimestamp) { + throw new Error('Remove this line and implement the function'); +} diff --git a/exercises/concept/appointment-time/appointment-time.spec.js b/exercises/concept/appointment-time/appointment-time.spec.js new file mode 100644 index 0000000000..1fd5d7b7fe --- /dev/null +++ b/exercises/concept/appointment-time/appointment-time.spec.js @@ -0,0 +1,120 @@ +import { describe, test, expect } from '@jest/globals'; + +import { + createAppointment, + getAppointmentTimestamp, + getAppointmentDetails, + isValid, + updateAppointment, + timeBetween, +} from './appointment-time'; + +describe('createAppointment', () => { + test('creates appointment 4 days in the future', () => { + const currentTime = Date.now(); + const expectedTime = currentTime + 345600 * 1000; + + expect(createAppointment(4, currentTime)).toEqual(new Date(expectedTime)); + }); + + test('creates appointment 124 in the future', () => { + const currentTime = Date.now(); + const expectedTime = currentTime + 10713600 * 1000; + + expect(createAppointment(124, currentTime)).toEqual(new Date(expectedTime)); + }); + + test('uses the passed in current time', () => { + const currentTime = Date.UTC(2000, 6, 16, 12, 0, 0, 0); + const result = createAppointment(0, currentTime); + + expect(result.getFullYear()).toEqual(2000); + }); + + test('uses the actual current time when it is not passed in', () => { + const result = createAppointment(0); + + expect(Math.abs(Date.now() - result.getTime())).toBeLessThanOrEqual( + // Maximum number of time zones difference + 27 * 60 * 60 * 1000, + ); + }); +}); + +describe('getAppointmentTimestamp', () => { + test('returns the correct timestamp', () => { + const currentTime = new Date(Date.UTC(2000, 6, 16, 12, 0, 0, 0)); + + expect(getAppointmentTimestamp(currentTime)).toEqual( + '2000-07-16T12:00:00.000Z', + ); + }); +}); + +describe('getAppointment', () => { + test('get appointment detail', () => { + expect(getAppointmentDetails('2022-04-24T08:15:00.000')).toStrictEqual({ + year: 2022, + month: 3, + date: 24, + hour: 8, + minute: 15, + }); + }); +}); + +describe('updateAppointment', () => { + test('should update with one option', () => { + expect( + updateAppointment('2022-02-09T09:20:00.000', { month: 6 }), + ).toStrictEqual({ year: 2022, month: 6, date: 9, hour: 9, minute: 20 }); + }); + + test('should update with multiple options', () => { + expect( + updateAppointment('2022-11-21T21:20:00.000', { + year: 2023, + month: 1, + date: 12, + hour: 1, + minute: 29, + }), + ).toStrictEqual({ year: 2023, month: 1, date: 12, hour: 1, minute: 29 }); + }); + + test('should update with option with zero as value', () => { + expect( + updateAppointment('2022-12-17T07:10:00.000', { minute: 0 }), + ).toStrictEqual({ year: 2022, month: 11, date: 17, hour: 7, minute: 0 }); + }); +}); + +describe('availableTimes', () => { + test('get available times between two appointments', () => { + expect( + timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000'), + ).toBe(515400); + }); +}); + +describe('isValid', () => { + test('true when appointment datetime is in the future', () => { + expect(isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000')).toBe( + true, + ); + }); + + test('true when appointment date is in the future', () => { + expect(isValid('2022-02-11', '2022-02-08')).toBe(true); + }); + + test('false when appointment datetime is in the past', () => { + expect(isValid('2022-05-20T23:00:00.000', '2023-02-08T23:00:00.000')).toBe( + false, + ); + }); + + test('false when appointment date is in the past', () => { + expect(isValid('2022-05-21', '2022-05-22')).toBe(false); + }); +}); diff --git a/exercises/concept/appointment-time/babel.config.js b/exercises/concept/appointment-time/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/appointment-time/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/appointment-time/eslint.config.mjs b/exercises/concept/appointment-time/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/appointment-time/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/appointment-time/jest.config.js b/exercises/concept/appointment-time/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/appointment-time/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/appointment-time/package.json b/exercises/concept/appointment-time/package.json new file mode 100644 index 0000000000..252180f6dc --- /dev/null +++ b/exercises/concept/appointment-time/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-concept-appointment-time", + "description": "Exercism concept exercise on appointment-time", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/appointment-time" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.16", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +}