From b003b56fc110814f56812dc7a2dbe8d70a14bed3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kryukov Date: Tue, 24 Feb 2026 12:43:47 +0300 Subject: [PATCH] Homework1-Typescript (1) --- src/homeworks/ts1/1_base.js | 65 ---------- src/homeworks/ts1/1_base.test.js | 45 +++---- src/homeworks/ts1/1_base.ts | 82 ++++++++++++ src/homeworks/ts1/2_repair.ts | 104 ++++++++------- src/homeworks/ts1/3_write.test.js | 63 +++++---- src/homeworks/ts1/3_write.ts | 208 ++++++++++++++++++++++-------- 6 files changed, 340 insertions(+), 227 deletions(-) delete mode 100644 src/homeworks/ts1/1_base.js create mode 100644 src/homeworks/ts1/1_base.ts diff --git a/src/homeworks/ts1/1_base.js b/src/homeworks/ts1/1_base.js deleted file mode 100644 index 611b3a92f..000000000 --- a/src/homeworks/ts1/1_base.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения - * */ -export const removePlus = (string) => string.replace(/^\+/, ''); - -export const addPlus = (string) => `+${string}`; - -export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); - -export const getBeautifulNumber = (value, separator = ' ') => - value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); - -export const round = (value, accuracy = 2) => { - const d = 10 ** accuracy; - return Math.round(value * d) / d; -}; - -const transformRegexp = - /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; - -export const getTransformFromCss = (transformCssString) => { - const data = transformCssString.match(transformRegexp); - if (!data) return { x: 0, y: 0 }; - return { - x: parseInt(data[6], 10), - y: parseInt(data[8], 10), - }; -}; - -export const getColorContrastValue = ([red, green, blue]) => - // http://www.w3.org/TR/AERT#color-contrast - Math.round((red * 299 + green * 587 + blue * 114) / 1000); - -export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); - -export const shortColorRegExp = /^#[0-9a-f]{3}$/i; -export const longColorRegExp = /^#[0-9a-f]{6}$/i; - -export const checkColor = (color) => { - if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); -}; - -export const hex2rgb = (color) => { - checkColor(color); - if (shortColorRegExp.test(color)) { - const red = parseInt(color.substring(1, 2), 16); - const green = parseInt(color.substring(2, 3), 16); - const blue = parseInt(color.substring(3, 4), 16); - return [red, green, blue]; - } - const red = parseInt(color.substring(1, 3), 16); - const green = parseInt(color.substring(3, 5), 16); - const blue = parseInt(color.substring(5, 8), 16); - return [red, green, blue]; -}; - -export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); - -export const transformCustomers = (customers) => { - return customers.reduce((acc, customer) => { - acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; - return acc; - }, {}); -}; diff --git a/src/homeworks/ts1/1_base.test.js b/src/homeworks/ts1/1_base.test.js index ee68dc16f..dbd708234 100644 --- a/src/homeworks/ts1/1_base.test.js +++ b/src/homeworks/ts1/1_base.test.js @@ -1,26 +1,21 @@ -// Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); +import { transformCustomers } from './1_base'; + +describe('all', () => { + it('transformCustomers', () => { + const customers = [ + { id: 1, name: 'John', age: 25, isSubscribed: true }, + { id: 2, name: 'Mary', age: 40, isSubscribed: false }, + { id: 3, name: 'Bob', age: 32, isSubscribed: true }, + { id: 4, name: 'Alice', age: 22, isSubscribed: true }, + { id: 5, name: 'David', age: 48, isSubscribed: false }, + ]; -// import { transformCustomers } from './1_base'; -// -// describe('all', () => { -// it('transformCustomers', () => { -// const customers = [ -// { id: 1, name: 'John', age: 25, isSubscribed: true }, -// { id: 2, name: 'Mary', age: 40, isSubscribed: false }, -// { id: 3, name: 'Bob', age: 32, isSubscribed: true }, -// { id: 4, name: 'Alice', age: 22, isSubscribed: true }, -// { id: 5, name: 'David', age: 48, isSubscribed: false }, -// ]; -// -// expect(transformCustomers(customers)).toEqual({ -// 1: { name: 'John', age: 25, isSubscribed: true }, -// 2: { name: 'Mary', age: 40, isSubscribed: false }, -// 3: { name: 'Bob', age: 32, isSubscribed: true }, -// 4: { name: 'Alice', age: 22, isSubscribed: true }, -// 5: { name: 'David', age: 48, isSubscribed: false }, -// }); -// }); -// }); + expect(transformCustomers(customers)).toEqual({ + 1: { name: 'John', age: 25, isSubscribed: true }, + 2: { name: 'Mary', age: 40, isSubscribed: false }, + 3: { name: 'Bob', age: 32, isSubscribed: true }, + 4: { name: 'Alice', age: 22, isSubscribed: true }, + 5: { name: 'David', age: 48, isSubscribed: false }, + }); + }); +}); diff --git a/src/homeworks/ts1/1_base.ts b/src/homeworks/ts1/1_base.ts new file mode 100644 index 000000000..b587b47c3 --- /dev/null +++ b/src/homeworks/ts1/1_base.ts @@ -0,0 +1,82 @@ +/** + * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения + * */ +export const removePlus = (value: string): string => value.replace(/^\+/, ''); + +export const addPlus = (value: string): string => `+${value}`; + +export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); + +export const getBeautifulNumber = (value: string, separator: string = ' '): string => + value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); + +export const round = (value: number, accuracy = 2) => { + const d = 10 ** accuracy; + return Math.round(value * d) / d; +}; + +const transformRegexp: RegExp = + /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; + +export interface Transform { + x: number; + y: number; +} + +export const getTransformFromCss = (transformCssString: string): Transform => { + const data = transformCssString.match(transformRegexp); + if (!data) return { x: 0, y: 0 }; + return { + x: parseInt(data[6], 10), + y: parseInt(data[8], 10), + }; +}; + +export type Rgb = [red: number, green: number, blue: number]; +export const getColorContrastValue = ([red, green, blue]: Rgb): number => + // http://www.w3.org/TR/AERT#color-contrast + Math.round((red * 299 + green * 587 + blue * 114) / 1000); + +export type ContrastType = 'black' | 'white'; +export const getContrastType = (contrastValue: number): ContrastType => (contrastValue > 125 ? 'black' : 'white'); + +export const shortColorRegExp: RegExp = /^#[0-9a-f]{3}$/i; +export const longColorRegExp: RegExp = /^#[0-9a-f]{6}$/i; + +export const checkColor = (color: string): void => { + if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); +}; + +export const hex2rgb = (color: string): Rgb => { + checkColor(color); + if (shortColorRegExp.test(color)) { + const red = parseInt(color.substring(1, 2), 16); + const green = parseInt(color.substring(2, 3), 16); + const blue = parseInt(color.substring(3, 4), 16); + return [red, green, blue]; + } + const red = parseInt(color.substring(1, 3), 16); + const green = parseInt(color.substring(3, 5), 16); + const blue = parseInt(color.substring(5, 8), 16); + return [red, green, blue]; +}; + +export type Numbered = { value: T; number: number }; +export const getNumberedArray = (arr: T[]): Numbered[] => arr.map((value, number) => ({ value, number })); + +export const toStringArray = (arr: Numbered[]): string[] => arr.map(({ value, number }) => `${value}_${number}`); + +export interface Customer { + id: string; + name: string; + age: number; + isSubscribed: boolean; +} +export type CustomersById = Record>; + +export const transformCustomers = (customers: Customer[]): CustomersById => { + return customers.reduce((acc, customer) => { + acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; + return acc; + }, {}); +}; diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 19e98c068..b22906dc0 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -1,47 +1,57 @@ -/** - * Здесь код с ошибками типов. Нужно их устранить - * */ - -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export const getFakeApi = async (): void => { -// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); -// console.log(result); -// }; -// -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export class SomeClass { -// constructor() { -// this.set = new Set([1]); -// this.channel = new BroadcastChannel('test-broadcast-channel'); -// } -// } -// -// export type Data = { -// type: 'Money' | 'Percent'; -// value: DataValue; -// }; -// -// export type DataValue = Money | Percent; -// -// export type Money = { -// currency: string; -// amount: number; -// }; -// -// export type Percent = { -// percent: number; -// }; -// -// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -// const getDataAmount = (data: Data): number => { -// switch (data.type) { -// case 'Money': -// return data.value.amount; -// -// default: { -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться -// throw new Error(`unknown type: ${data.type}`); -// } -// } -// }; +export interface Todo { + userId: number; + id: number; + title: string; + completed: boolean; +} + +export const getFakeApi = async (): Promise => { + const result: Todo = await fetch('https://jsonplaceholder.typicode.com/todos/1').then( + (response) => response.json() as Promise + ); + console.log(result); +}; + +export class SomeClass { + set: Set; + channel: BroadcastChannel; + + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } +} + +export type Data = + | { + type: 'Money'; + value: Money; + } + | { + type: 'Percent'; + value: Percent; + }; + +export type Money = { + currency: string; + amount: number; +}; + +export type Percent = { + percent: number; +}; + +const getDataAmount = (data: Data): number => { + switch (data.type) { + case 'Money': + return data.value.amount; + + case 'Percent': + return data.value.percent; + + default: { + const unhandled: never = data; + throw new Error(`unknown type: ${unhandled}`); + } + } +}; diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 5b5173063..a11ac89c3 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,35 +1,30 @@ -// Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); +import { createRandomOperation, createRandomProduct } from './3_write'; + +describe('all', () => { + it('operation', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const operation = createRandomOperation(createdAt); + expect(operation).toHaveProperty('createdAt', createdAt); + expect(operation).toHaveProperty('id'); + expect(operation).toHaveProperty('name'); + expect(operation).toHaveProperty('desc'); + expect(operation).toHaveProperty('createdAt'); + expect(operation).toHaveProperty('amount'); + expect(operation).toHaveProperty('category'); + expect(operation).toHaveProperty('type'); + }); -// import { createRandomOperation, createRandomProduct } from './3_write'; -// -// describe('all', () => { -// it('operation', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const operation = createRandomOperation(createdAt); -// expect(operation).toHaveProperty('createdAt', createdAt); -// expect(operation).toHaveProperty('id'); -// expect(operation).toHaveProperty('name'); -// expect(operation).toHaveProperty('desc'); -// expect(operation).toHaveProperty('createdAt'); -// expect(operation).toHaveProperty('amount'); -// expect(operation).toHaveProperty('category'); -// expect(operation).toHaveProperty('type'); -// }); -// -// it('product', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const product = createRandomProduct(createdAt); -// expect(product).toHaveProperty('createdAt', createdAt); -// expect(product).toHaveProperty('id'); -// expect(product).toHaveProperty('name'); -// expect(product).toHaveProperty('photo'); -// expect(product).toHaveProperty('desc'); -// expect(product).toHaveProperty('createdAt'); -// expect(product).toHaveProperty('oldPrice'); -// expect(product).toHaveProperty('price'); -// expect(product).toHaveProperty('category'); -// }); -// }); + it('product', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const product = createRandomProduct(createdAt); + expect(product).toHaveProperty('createdAt', createdAt); + expect(product).toHaveProperty('id'); + expect(product).toHaveProperty('name'); + expect(product).toHaveProperty('photo'); + expect(product).toHaveProperty('desc'); + expect(product).toHaveProperty('createdAt'); + expect(product).toHaveProperty('oldPrice'); + expect(product).toHaveProperty('price'); + expect(product).toHaveProperty('category'); + }); +}); diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 15f9dcdf2..ef032bd6b 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -1,56 +1,152 @@ -/** - * Функции написанные здесь пригодятся на последующих уроках - * С помощью этих функций мы будем добавлять элементы в список для проверки динамической загрузки - * Поэтому в идеале чтобы функции возвращали случайные данные, но в то же время не абракадабру. - * В целом сделайте так, как вам будет удобно. - * */ - -/** - * Нужно создать тип Category, он будет использоваться ниже. - * Категория содержит - * - id (строка) - * - name (строка) - * - photo (строка, необязательно) - * - * Продукт (Product) содержит - * - id (строка) - * - name (строка) - * - photo (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - oldPrice (число, необязательно) - * - price (число) - * - category (Категория) - * - * Операция (Operation) может быть либо тратой (Cost), либо доходом (Profit) - * - * Трата (Cost) содержит - * - id (строка) - * - name (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - amount (число) - * - category (Категория) - * - type ('Cost') - * - * Доход (Profit) содержит - * - id (строка) - * - name (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - amount (число) - * - category (Категория) - * - type ('Profit') - * */ - -/** - * Создает случайный продукт (Product). - * Принимает дату создания (строка) - * */ -// export const createRandomProduct = (createdAt: string) => {}; - -/** - * Создает случайную операцию (Operation). - * Принимает дату создания (строка) - * */ -// export const createRandomOperation = (createdAt: string) => {}; +export type Category = { + id: string; + name: string; + photo?: string; +}; + +export type Product = { + id: string; + name: string; + photo: string; + desc?: string; + createdAt: string; + oldPrice?: number; + price: number; + category: Category; +}; + +export type OperationBase = { + id: string; + name: string; + desc?: string; + createdAt: string; + amount: number; + category: Category; +}; + +export type Cost = OperationBase & { type: 'Cost' }; +export type Profit = OperationBase & { type: 'Profit' }; + +export type Operation = Cost | Profit; + +/* ---------------- helpers ---------------- */ + +const pick = (arr: readonly T[]): T => arr[Math.floor(Math.random() * arr.length)]; +const chance = (p: number): boolean => Math.random() < p; + +const randomMoney = (min: number, max: number): number => Math.round((Math.random() * (max - min) + min) * 100) / 100; + +const randomId = (prefix = ''): string => { + const s1 = Math.random().toString(36).slice(2, 10); + const s2 = Date.now().toString(36); + return `${prefix}${s2}_${s1}`; +}; + +const photoUrl = (seed: string, w = 640, h = 480): string => + `https://picsum.photos/seed/${encodeURIComponent(seed)}/${w}/${h}`; + +const maybeDesc = (): string | undefined => { + if (!chance(0.5)) return undefined; + return pick([ + 'Без лишних деталей.', + 'Срочно/по пути домой.', + 'Акция/скидка применена.', + 'Плановая покупка.', + 'Рекомендовано друзьями.', + ] as const); +}; + +/* ---------------- categories ---------------- */ + +export const CATEGORIES = [ + { id: 'cat_food', name: 'Еда', photo: photoUrl('cat_food', 200, 200) }, + { id: 'cat_tech', name: 'Техника', photo: photoUrl('cat_tech', 200, 200) }, + { id: 'cat_clothes', name: 'Одежда' }, + { id: 'cat_home', name: 'Дом', photo: photoUrl('cat_home', 200, 200) }, + { id: 'cat_transport', name: 'Транспорт' }, + { id: 'cat_fun', name: 'Развлечения' }, + { id: 'cat_health', name: 'Здоровье' }, + { id: 'cat_gifts', name: 'Подарки' }, + { id: 'cat_edu', name: 'Образование' }, + { id: 'cat_subs', name: 'Подписки' }, +] as const satisfies readonly Category[]; + +const createRandomCategory = (): Category => { + const cat = pick(CATEGORIES); + return { ...cat }; +}; + +/* ---------------- data generators ---------------- */ + +const PRODUCT_NAMES = [ + 'Кофе', + 'Наушники', + 'Футболка', + 'Лампа', + 'Такси', + 'Книга', + 'Пылесос', + 'Мышь', + 'Шампунь', + 'Рюкзак', +] as const; + +const OP_COST_NAMES = [ + 'Покупка продуктов', + 'Проезд', + 'Подписка', + 'Кафе', + 'Аптека', + 'Одежда', + 'Домашние товары', + 'Подарок', +] as const; + +const OP_PROFIT_NAMES = ['Зарплата', 'Возврат', 'Фриланс', 'Кэшбек', 'Продажа вещи', 'Подарили деньги'] as const; + +export const createRandomProduct = (createdAt: string): Product => { + const id = randomId('prd_'); + const category = createRandomCategory(); + + const price = randomMoney(2, 499); + const oldPrice = chance(0.35) ? Math.max(price + randomMoney(1, 120), price) : undefined; + + return { + id, + name: pick(PRODUCT_NAMES), + photo: photoUrl(id, 640, 480), + desc: maybeDesc(), + createdAt, + oldPrice, + price, + category, + }; +}; + +export const createRandomOperation = (createdAt: string): Operation => { + const isProfit = chance(0.5); + const id = randomId(isProfit ? 'prf_' : 'cst_'); + const category = createRandomCategory(); + + const amount = isProfit ? randomMoney(10, 6000) : randomMoney(1, 1200); + + return isProfit + ? { + id, + name: pick(OP_PROFIT_NAMES), + desc: maybeDesc(), + createdAt, + amount, + category, + type: 'Profit', + } + : { + id, + name: pick(OP_COST_NAMES), + desc: maybeDesc(), + createdAt, + amount, + category, + type: 'Cost', + }; +};