diff --git a/README.md b/README.md index 74a5919..da786d4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A comprehensive collection of TypeScript utility functions for modern web develo ## Features -- ๐Ÿ› ๏ธ **Comprehensive**: String, object, cookie, number, validation, format, search query, and common utilities +- ๐Ÿ› ๏ธ **Comprehensive**: String, object, cookie, number, validation, format, search query, device, and common utilities - ๐Ÿ“ฆ **Tree-shakable**: Import only what you need - ๐Ÿ”’ **Type-safe**: Full TypeScript support with type definitions - โšก **Lightweight**: Minimal dependencies and optimized for performance @@ -32,6 +32,7 @@ import { commonUtil, formatUtil, searchQueryUtil, + deviceUtil, } from "kr-corekit"; // String utilities @@ -72,6 +73,9 @@ const copied = await commonUtil.copyToClipboard("Hello, World!"); // true if suc // Search Query utilities const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" } +// Device utilities +const device = deviceUtil.getDevice(); // { isMobile: false, isTablet: false, isDesktop: true, isIOS: false, isAndroid: false } + // Cookie utilities cookieUtil.setCookie("theme", "dark"); const theme = cookieUtil.getCookie("theme"); @@ -121,6 +125,10 @@ const formattedPhone = formatUtil.formatPhoneNumber("01012345678"); // "010-1234 - `getAllQuery(): Record` - Parses the current URL's query string and returns an object with key-value pairs. Values appear as arrays when the same key is used multiple times. +### DeviceUtil + +- `getDevice(): DeviceInfo` - Detects the user's device environment. Returns information about device type (mobile/tablet/desktop) and operating system (iOS/Android). Uses navigator.userAgent for detection and provides safe fallback for SSR environments. + ### CookieUtil - `setCookie(name: string, value: string, options?: object): void` - Sets a cookie diff --git a/package/deviceUtil/getDevice/index.test.ts b/package/deviceUtil/getDevice/index.test.ts new file mode 100644 index 0000000..586758a --- /dev/null +++ b/package/deviceUtil/getDevice/index.test.ts @@ -0,0 +1,83 @@ +/** + * vitest๊ฐ€ JSDOM์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + * ์ด๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ window, navigator ๋“ฑ์˜ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * + * @vitest-environment jsdom + */ +import { describe, test, expect, afterEach } from "vitest"; +import getDevice from "."; + +describe("getDevice", () => { + // userAgent๋ฅผ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋งˆ๋‹ค ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•œ ํ—ฌํผ ํ•จ์ˆ˜ + const mockUserAgent = (userAgent: string) => { + // JSDOM์ด ์ƒ์„ฑํ•œ window.navigator ๊ฐ์ฒด์˜ userAgent ์†์„ฑ์„ ๋ฎ์–ด์”๋‹ˆ๋‹ค. + Object.defineProperty(window.navigator, "userAgent", { + value: userAgent, + writable: true, + configurable: true, + }); + }; + + // ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚œ ํ›„, ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก userAgent๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. + afterEach(() => { + mockUserAgent(""); + }); + + describe("ํด๋ผ์ด์–ธํŠธ (๋ธŒ๋ผ์šฐ์ €) ํ™˜๊ฒฝ", () => { + describe("๋ฐ์Šคํฌํ†ฑ", () => { + test("Windows Chrome User Agent๋ฅผ ๋ฐ์Šคํฌํ†ฑ์œผ๋กœ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค", () => { + mockUserAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + ); + const { isDesktop, isMobile, isTablet } = getDevice(); + expect(isDesktop).toBe(true); + expect(isMobile).toBe(false); + expect(isTablet).toBe(false); + }); + }); + + describe("๋ชจ๋ฐ”์ผ", () => { + test("iPhone User Agent๋ฅผ ๋ชจ๋ฐ”์ผ ๋ฐ iOS๋กœ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค", () => { + mockUserAgent( + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + ); + const { isMobile, isIOS, isDesktop } = getDevice(); + expect(isMobile).toBe(true); + expect(isIOS).toBe(true); + expect(isDesktop).toBe(false); + }); + + test("Android Phone User Agent๋ฅผ ๋ชจ๋ฐ”์ผ ๋ฐ Android๋กœ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค", () => { + mockUserAgent( + "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36" + ); + const { isMobile, isAndroid, isTablet } = getDevice(); + expect(isMobile).toBe(true); + expect(isAndroid).toBe(true); + expect(isTablet).toBe(false); + }); + }); + + describe("ํƒœ๋ธ”๋ฆฟ", () => { + test("iPad User Agent๋ฅผ ํƒœ๋ธ”๋ฆฟ ๋ฐ iOS๋กœ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค", () => { + mockUserAgent( + "Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1" + ); + const { isTablet, isIOS, isMobile } = getDevice(); + expect(isTablet).toBe(true); + expect(isIOS).toBe(true); + expect(isMobile).toBe(false); + }); + + test("Android Tablet User Agent๋ฅผ ํƒœ๋ธ”๋ฆฟ ๋ฐ Android๋กœ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค", () => { + mockUserAgent( + "Mozilla/5.0 (Linux; Android 12; SM-X906C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.99 Safari/537.36" + ); + const { isTablet, isAndroid, isMobile } = getDevice(); + expect(isTablet).toBe(true); + expect(isAndroid).toBe(true); + expect(isMobile).toBe(false); + }); + }); + }); +}); diff --git a/package/deviceUtil/getDevice/index.ts b/package/deviceUtil/getDevice/index.ts new file mode 100644 index 0000000..4d3b8d6 --- /dev/null +++ b/package/deviceUtil/getDevice/index.ts @@ -0,0 +1,33 @@ +/** + * @typedef {object} DeviceInfo + * @property {boolean} isMobile - ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค ์—ฌ๋ถ€ + * @property {boolean} isTablet - ํƒœ๋ธ”๋ฆฟ ๋””๋ฐ”์ด์Šค ์—ฌ๋ถ€ + * @property {boolean} isDesktop - ๋ฐ์Šคํฌํ†ฑ ๋””๋ฐ”์ด์Šค ์—ฌ๋ถ€ + * @property {boolean} isIOS - iOS ์šด์˜์ฒด์ œ ์—ฌ๋ถ€ + * @property {boolean} isAndroid - ์•ˆ๋“œ๋กœ์ด๋“œ ์šด์˜์ฒด์ œ ์—ฌ๋ถ€ + */ + +/** + * ์‚ฌ์šฉ์ž์˜ ๋””๋ฐ”์ด์Šค ํ™˜๊ฒฝ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * window.navigator.userAgent๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„์„ํ•˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ๋งŒ ์ •ํ™•ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * + * @returns {DeviceInfo} ๋””๋ฐ”์ด์Šค ํ™˜๊ฒฝ ์ •๋ณด ๊ฐ์ฒด + */ +export default function getDevice() { + // * ----- ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์ผ ๊ฒฝ์šฐ, ์‹คํ–‰ ----- * // + const userAgent = window.navigator.userAgent; + + const isIOS = /iPhone|iPad|iPod/i.test(userAgent); + + const isAndroid = /Android/i.test(userAgent); + + const isTablet = /(iPad)|(tablet)|(android(?!.*mobi))/i.test(userAgent); + + const isMobile = + !isTablet && + /Mobi|iP(hone|od)|Android|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); + + const isDesktop = !isMobile && !isTablet; + + return { isMobile, isTablet, isDesktop, isIOS, isAndroid }; +} diff --git a/package/deviceUtil/index.ts b/package/deviceUtil/index.ts new file mode 100644 index 0000000..8fc5794 --- /dev/null +++ b/package/deviceUtil/index.ts @@ -0,0 +1 @@ +export { default as getDevice } from "./getDevice"; diff --git a/vitest-report.xml b/vitest-report.xml index 83e546b..9c0abde 100644 --- a/vitest-report.xml +++ b/vitest-report.xml @@ -1,15 +1,265 @@ - - - + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +