({
8 | ...DefaultTheme,
9 | enhanceApp: ({ app }) => {
10 | // globals.forEach(([name, Comp]) => {
11 | // app.component(name, Comp)
12 | // })
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/styles/overrides.css:
--------------------------------------------------------------------------------
1 | img {
2 | display: inline !important;
3 | }
4 |
5 | :root {
6 | --vp-home-hero-name-color: transparent;
7 | --vp-home-hero-name-background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff);
8 | --vp-home-hero-image-background-image: linear-gradient(-45deg,
9 | #4acf93 30%,
10 | #c3edda);
11 | --vp-home-hero-image-filter: blur(100px);
12 | }
13 |
--------------------------------------------------------------------------------
/docs/challenges.md:
--------------------------------------------------------------------------------
1 | # Challenges
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 |
2 | # 📖 Getting Started
3 |
4 | I love `Vue.js` ♥️. It's so Awesome 🚀. It helps me finish my work well and build applications.
5 |
6 | I was looking for a project for everyone to learn `Vue.js` together, and that's why this project was born. I believe we can grow together here, and hope it helps you.
7 |
8 | This project is aimed at helping you better understand `Vue.js`, writing your own utilities, or just having fun with the challenges.
9 |
10 | We are also trying to form a community where you can ask questions and get answers you have faced in the real world - they may become part of the challenges!
11 |
12 | **Let’s go,Have a nice trip ! ♥️**
13 |
--------------------------------------------------------------------------------
/docs/guide/contribution.md:
--------------------------------------------------------------------------------
1 | # 🤝 How to Contribute
2 |
3 | There are several ways you can contribute to this project
4 |
5 | - Share your answers/solutions
6 | - Propose new challenges
7 | - Add more test cases to the existing challenges
8 | - Provide learning resources or ideas of how to solve challenges
9 | - Share the problems you have faced in real-world projects, regardless you have the solution or not - the community would help you as well.
10 | - Help others by discussing issues
11 |
12 | Just [open an issue](https://github.com/webfansplz/vuejs-challenges/issues/new/choose) and choose the corresponding template. Thanks!
13 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 | sidebar: false
4 |
5 | title: Vue.js challenges
6 | titleTemplate: A Vue.js online challenge platform
7 |
8 | hero:
9 | name: Vue.js challenges
10 | text: Collection of Vue.js challenges
11 | tagline: A Vue.js online challenge platform
12 | image:
13 | src: logo.png
14 | alt: Vue.js challenges
15 | actions:
16 | - theme: brand
17 | text: Get Started
18 | link: /getting-started
19 | - theme: alt
20 | text: Challenges
21 | link: /challenges
22 | - theme: alt
23 | text: View on Github
24 | link: https://github.com/webfansplz/vuejs-challenges
25 |
26 | features:
27 | - title: Online Play
28 | details: Take the challenges based on the online Vue.js SFC Playground
29 | - title: Wide Coverage
30 | details: Reactivity API,Composable Function,Directives,Components
31 | - title: Difficulty level
32 | details: You can choose the challenge from different levels of difficulty
33 | - title: Automated contribution
34 | details: Propose a new challenge in the issue, a PR will be auto generated
35 | ---
36 |
--------------------------------------------------------------------------------
/docs/meta/index.ts:
--------------------------------------------------------------------------------
1 | export { challenges, challengesByDifficulty } from "./challenges.json"
2 | export { categorys, navMenu } from "./challenges-nav-menu.json"
3 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "webfansplz",
3 | "description": "",
4 | "devDependencies": {
5 | "sass": "^1.53.0",
6 | "unplugin-vue-components": "^0.21.0",
7 | "vite": "^2.9.13",
8 | "vitepress": "1.0.0-alpha.4"
9 | },
10 | "license": "MIT",
11 | "private": true,
12 | "scripts": {
13 | "build": "npm run update && vitepress build .",
14 | "dev": "npm run update && vitepress dev . --host",
15 | "serve": "vitepress serve . --port 5001",
16 | "update": "tsx ./scripts/update.ts"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webfansplz/vuejs-challenges/b3026f0a34c4a588876c6fd7ad9b49f3a837401e/docs/public/favicon.ico
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webfansplz/vuejs-challenges/b3026f0a34c4a588876c6fd7ad9b49f3a837401e/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/questions/323-prop-validation/README.md:
--------------------------------------------------------------------------------
1 | Prop Validation By Lov`u`e @heappynd
2 |
3 |
4 | Please validate the `type` prop of the `Button` component. it's accept the following strings `primary | ghost | dashed | link | text | default` only and the default value is `default`.
5 |
6 | ```vue
7 |
12 |
13 |
14 | Button
15 |
16 | ```
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "esnext",
5 | "target": "esnext",
6 | "lib": ["DOM", "ESNext"],
7 | "strict": true,
8 | "jsx": "preserve",
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "types": [
17 | "vite/client",
18 | "vitepress"
19 | ],
20 | "paths": {
21 | "~/*": ["src/*"]
22 | }
23 | },
24 | "include": [
25 | "./*.ts",
26 | ".vitepress/**/*.ts",
27 | ".vitepress/**/*.vue"
28 | ],
29 | "exclude": ["dist", "node_modules"]
30 | }
31 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from "path"
2 | import { defineConfig } from "vite"
3 | import Components from "unplugin-vue-components/vite"
4 | import { MarkdownTransform } from "./.vitepress/plugins/markdownTransform"
5 |
6 | export default defineConfig({
7 | server: {
8 | hmr: {
9 | overlay: false,
10 | },
11 | fs: {
12 | allow: [resolve(__dirname, "..")],
13 | },
14 | },
15 |
16 | plugins: [
17 | MarkdownTransform(),
18 | Components({
19 | include: [/\.vue/, /\.md/],
20 | dirs: ".vitepress/components",
21 | dts: ".vitepress/components.d.ts",
22 | }),
23 | ],
24 |
25 | css: { preprocessorOptions: { scss: { charset: false } } },
26 | })
27 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webfansplz/vuejs-challenges/b3026f0a34c4a588876c6fd7ad9b49f3a837401e/logo.png
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build.environment]
2 | NPM_FLAGS = "--version"
3 | NODE_VERSION = "16"
4 |
5 | [build]
6 | publish = "docs/.vitepress/dist"
7 | command = "npx pnpm i --store=node_modules/.pnpm-store && npm run docs:build"
8 |
9 | [[redirects]]
10 | from = "/*"
11 | to = "/index.html"
12 | status = 200
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuejs-challenges",
3 | "description": "Collection of Vue.js challenges",
4 | "keywords": ["Vue.js challenges", "Vue.js", "challenges", "Learn Vue.js"],
5 | "license": "MIT",
6 | "private":true,
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/webfansplz/vuejs-challenges.git"
10 | },
11 | "author": "",
12 | "main": "index.js",
13 | "scripts": {
14 | "test": "vitest --environment jsdom --ui",
15 | "build": "tsx ./scripts/build.ts",
16 | "docs:dev": "pnpm run -C docs update && pnpm run -C docs dev",
17 | "docs:build": "pnpm run -C docs build",
18 | "build:all": "pnpm run build && pnpm run docs:build",
19 | "docs:serve": "pnpm run -C docs serve",
20 | "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx,.json --max-warnings 0",
21 | "lint:fix": "eslint --fix . --ext .vue,.js,.ts,.jsx,.tsx,.json"
22 | },
23 | "bugs": {
24 | "url": "https://github.com/webfansplz/vuejs-challenges/issues"
25 | },
26 | "homepage": "https://github.com/webfansplz/vuejs-challenges#readme",
27 | "devDependencies": {
28 | "@types/fs-extra": "^9.0.13",
29 | "@types/js-yaml": "^4.0.5",
30 | "@types/jsdom": "^16.2.14",
31 | "@types/node": "^18.0.0",
32 | "@vitejs/plugin-vue": "^2.3.3",
33 | "@vitest/ui": "^0.17.0",
34 | "@vue/test-utils": "^2.0.2",
35 | "@webfansplz/eslint-config": "^0.1.0",
36 | "eslint": "^8.18.0",
37 | "fast-glob": "^3.2.11",
38 | "fs-extra": "^10.1.0",
39 | "js-yaml": "^4.1.0",
40 | "jsdom": "^20.0.0",
41 | "pnpm": "7.3.0",
42 | "tsx": "^3.6.0",
43 | "typescript": "^4.7.4",
44 | "vitest": "^0.17.0",
45 | "vue": "^3.2.37"
46 | },
47 | "stackblitz": {
48 | "startCommand": "npm run test"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - docs
3 | - scripts
4 |
--------------------------------------------------------------------------------
/questions/1-hello-word/App.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
msg
9 |
10 |
11 |
--------------------------------------------------------------------------------
/questions/1-hello-word/README.md:
--------------------------------------------------------------------------------
1 | Hello World By webfansplz @webfansplz
2 |
3 | Hello, World!
4 |
5 | In Vue.js Challenges, we use the Vue.js SFC Playground based on [vuejs/repl](https://github.com/vuejs/repl) to code online and provided coding judge by `StackBlitz` & [Vitest](https://github.com/vitest-dev/vitest).
6 |
7 | For this challenge, you will need to change the following code to make the page show "Hello World" correctly.
8 |
9 | ```vue
10 |
14 |
15 |
16 |
17 |
18 |
msg
19 |
20 |
21 |
22 | ```
23 |
24 | Click the `Take the Challenge` button to start coding! Happy Hacking!
25 |
26 |
27 |
--------------------------------------------------------------------------------
/questions/1-hello-word/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 你好 ! By webfansplz @webfansplz
2 |
3 | Hello,World!
4 |
5 | 在这个挑战中,我们使用基于 [vuejs/repl](https://github.com/vuejs/repl) 的`SFC`编码游乐场进行在线编码且通过`StackBlitz` 和 [Vitest](https://github.com/vitest-dev/vitest)进行挑战判断。
6 |
7 | 对于这个挑战,您将需要更改以下代码,以使页面正确显示“Hello World”。
8 |
9 | ```vue
10 |
14 |
15 |
16 |
17 |
18 |
msg
19 |
20 |
21 |
22 | ```
23 |
24 |
25 | 点击上方的 `接受挑战` 开始编码!旅途愉快!
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/questions/1-hello-word/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import HelloWorld from "./App.vue"
5 |
6 | interface HelloWorldProps {
7 | msg: string
8 | }
9 |
10 | describe("HelloWorld", () => {
11 | it("renders a 'Hello World'", () => {
12 | const wrapper = mount(HelloWorld)
13 | const msg = (wrapper.vm as unknown as HelloWorldProps).msg
14 | expect(wrapper.text()).toBe(msg)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/questions/1-hello-word/info.yml:
--------------------------------------------------------------------------------
1 | title: Hello World
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: warm
8 |
--------------------------------------------------------------------------------
/questions/1-hello-word/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 你好 !
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/10-lifecycle/App.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Toggle Child Component
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/questions/10-lifecycle/Child.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 | Child Component: {{ count }}
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/questions/10-lifecycle/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import LifecycleFather from "./App.vue"
5 | import LifecycleChild from "./Child.vue"
6 |
7 | function delay(timeout: number) {
8 | return new Promise((resolve) => {
9 | setTimeout(resolve, timeout)
10 | })
11 | }
12 |
13 | interface InjectType {
14 | count: number
15 | timer: number
16 | }
17 |
18 | describe("Lifecycle", () => {
19 | it("The timer will work abnormally when the child component is toggled", async() => {
20 | const fatherWrapper = mount(LifecycleFather)
21 | const childWrapper = fatherWrapper.findComponent(LifecycleChild)
22 | const button = fatherWrapper.find("button")
23 | expect(childWrapper.exists()).toBeTruthy()
24 |
25 | await delay(1000)
26 | const firstchildCount = (childWrapper.vm as unknown as InjectType).count
27 | expect(firstchildCount).toMatchInlineSnapshot("1")
28 |
29 | await button.trigger("click")
30 | expect(childWrapper.exists()).not.toBeTruthy()
31 |
32 | await delay(1000)
33 | const secondChildCount = (childWrapper.vm as unknown as InjectType).count
34 | expect(secondChildCount).toMatchInlineSnapshot("1")
35 |
36 | await button.trigger("click")
37 | expect(fatherWrapper.findComponent(LifecycleChild).exists()).toBeTruthy()
38 |
39 | await delay(1000)
40 | const thirdChildCount = (childWrapper.vm as unknown as InjectType).count
41 | expect(thirdChildCount).toMatchInlineSnapshot("2")
42 |
43 | await delay(1000)
44 | const fourthChildCount = (childWrapper.vm as unknown as InjectType).count
45 | expect(fourthChildCount).toMatchInlineSnapshot("3")
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/questions/10-lifecycle/info.yml:
--------------------------------------------------------------------------------
1 | title: Lifecycle Hooks
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Lifecycle
10 |
--------------------------------------------------------------------------------
/questions/10-lifecycle/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 生命周期钩子
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/11-next-dom-update/App.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {{ count }}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/questions/11-next-dom-update/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 下一次DOM更新 By webfansplz @webfansplz
2 |
3 |
4 | 在`Vue.js`中改变响应式状态时,DOM不会同步更新。
5 | `Vue.js` 提供了一个用于等待下一次DOM更新的方法,让我们开始吧 👇:
6 |
7 | ```vue
8 |
24 |
25 |
26 |
27 | {{ count }}
28 |
29 |
30 |
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/questions/11-next-dom-update/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("next-dom-update", () => {
7 | it("should work'", async() => {
8 | let printLog = ''
9 | console.log = vi.fn(
10 | (log: string) => {
11 | printLog = log?.toString()?.trim()
12 | })
13 | const wrapper = mount(App)
14 |
15 | expect(wrapper.text()).toMatchInlineSnapshot("\"0\"")
16 |
17 | const button = wrapper.find("button")
18 | await button.trigger("click")
19 |
20 | expect(wrapper.text()).toMatchInlineSnapshot('"1"')
21 | expect(printLog).toMatchInlineSnapshot('"true"')
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/questions/11-next-dom-update/info.yml:
--------------------------------------------------------------------------------
1 | title: Next DOM update flush
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Global API:General
10 |
--------------------------------------------------------------------------------
/questions/11-next-dom-update/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 下一次DOM更新
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/App.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Make it never change: {{ count }}
13 |
14 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/README.md:
--------------------------------------------------------------------------------
1 | Optimize performance directive By webfansplz @webfansplz
2 |
3 |
4 | `Vue.js` provides a directive that renders the element and the component only once, and skips future updates.
5 |
6 | Do you know what the directive is?. Lets try it 👇:
7 |
8 | ```vue
9 |
18 |
19 |
20 | Make it not to change: {{ count }}
21 |
22 |
23 | ```
24 |
25 |
26 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 优化性能的指令 By webfansplz @webfansplz
2 |
3 |
4 | `Vue.js` 提供了一个指令,以便只渲染一次元素和组件,并且跳过以后的更新。
5 |
6 | 你知道它是什么吗 ? 让我们试试👇:
7 |
8 | ```vue
9 |
18 |
19 |
20 | 使它从不更新: {{ count }}
21 |
22 |
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import OptimizePerfDirective from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("OptimizePerfDirective", () => {
13 | it("should work", async() => {
14 | const wrapper = mount(OptimizePerfDirective)
15 | await delay(1100)
16 | const content = +wrapper.vm.$el.innerHTML.replace(/\D/g, "")
17 | expect(content).toBe(0)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/info.yml:
--------------------------------------------------------------------------------
1 | title: Optimize performance directive
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Directives,Built-ins
10 |
--------------------------------------------------------------------------------
/questions/12-optimize-perf-directive/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 优化性能的指令
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/App.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | {{ msg }}
10 |
11 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/README.md:
--------------------------------------------------------------------------------
1 | DOM Portal By webfansplz @webfansplz
2 |
3 |
4 | `Vue.js` provides a built-in component that renders its slot content to another part of the `DOM`.
5 |
6 | Do you know the built-in component?. Lets try it 👇:
7 |
8 | ```vue
9 |
14 |
15 |
16 |
17 | {{ msg }}
18 |
19 |
20 |
21 | ```
22 |
23 |
24 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | DOM传送门 By webfansplz @webfansplz
2 |
3 |
4 | `Vue.js`提供了一个内置组件,将其插槽内容渲染到另一个DOM,成为该DOM的一部分。
5 |
6 | 你知道它是什么吗 ? 让我们试试👇:
7 |
8 | ```vue
9 |
14 |
15 |
16 |
17 | {{ msg }}
18 |
19 |
20 |
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("DomPortal", () => {
7 | it("render to body", () => {
8 | const wrapper = mount(App)
9 | expect(wrapper.find("span").exists()).toBeFalsy()
10 | expect(document.body.innerHTML).toMatchInlineSnapshot("\"Hello World \"")
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/info.yml:
--------------------------------------------------------------------------------
1 | title: DOM Portal
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Components,Built-ins
10 |
--------------------------------------------------------------------------------
/questions/13-dom-portal/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: DOM传送门
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/App.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | hello
15 |
16 |
17 |
23 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/README.md:
--------------------------------------------------------------------------------
1 | Dynamic css values By webfansplz @webfansplz
2 |
3 |
4 | SFC `
31 |
32 | ```
33 |
34 |
35 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 动态CSS By webfansplz @webfansplz
2 |
3 |
4 |
5 |
6 | `Vue`单文件组件 `
33 |
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest"
2 |
3 | import AppRaw from "./App.vue?raw"
4 |
5 | describe("DomPortal", () => {
6 | it("render to body", () => {
7 | expect(AppRaw).toContain(atob("Y29sb3I6IHYtYmluZCh0aGVtZSk="))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/info.yml:
--------------------------------------------------------------------------------
1 | title: Dynamic css values
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: CSS Features
10 |
--------------------------------------------------------------------------------
/questions/14-dynamic-css-values/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 动态CSS
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/15-useToggle/App.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | State: {{ state ? 'ON' : 'OFF' }}
17 |
18 | Toggle state
19 |
20 |
21 |
--------------------------------------------------------------------------------
/questions/15-useToggle/README.md:
--------------------------------------------------------------------------------
1 | useToggle By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, we'll start by creating a composable function. Lets start with `useToggle` 👇:
5 |
6 |
7 | ```vue
8 |
21 |
22 |
23 | State: {{ state ? 'ON' : 'OFF' }}
24 |
25 | Toggle state
26 |
27 |
28 |
29 | ```
30 |
31 |
32 |
--------------------------------------------------------------------------------
/questions/15-useToggle/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 切换器 By webfansplz @webfansplz
2 |
3 |
4 | 这个挑战开始,我们将尝试编写可组合函数,让我们从`useToggle`开始 👇:
5 |
6 | ```vue
7 |
20 |
21 |
22 | State: {{ state ? 'ON' : 'OFF' }}
23 |
24 | Toggle state
25 |
26 |
27 |
28 | ```
29 |
30 |
--------------------------------------------------------------------------------
/questions/15-useToggle/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("useToggle", () => {
7 | it("toggle state", async() => {
8 | const wrapper = mount(App)
9 | const p1 = wrapper.findAll("p")[0]
10 | const p2 = wrapper.findAll("p")[1]
11 | expect(p1.text()).toBe("State: OFF")
12 |
13 | await p2.trigger("click")
14 | expect(p1.text()).toBe("State: ON")
15 |
16 | await p2.trigger("click")
17 | expect(p1.text()).toBe("State: OFF")
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/questions/15-useToggle/info.yml:
--------------------------------------------------------------------------------
1 | title: useToggle
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Composable Function
10 |
--------------------------------------------------------------------------------
/questions/15-useToggle/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 切换器
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/16-until/App.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 | Increase
34 |
35 |
36 |
--------------------------------------------------------------------------------
/questions/16-until/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | until By webfansplz @webfansplz
2 |
3 |
4 | 有些时候,我们需要依赖于异步的返回结果做一些后续处理,`until`函数在这种场景下非常有用,你能实现它吗 ? 让我们来试试吧 👇:
5 |
6 |
7 | ```vue
8 |
37 |
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/questions/16-until/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("until", () => {
13 | it("should work", async() => {
14 | const result: string[] = []
15 | console.log = vi.fn((log: string) => {
16 | result.push(log)
17 | })
18 |
19 | const wrapper = mount(App)
20 |
21 | await wrapper.find("p").trigger("click")
22 | await delay(4000)
23 | expect(JSON.stringify(result)).toBe("[true]")
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/questions/16-until/info.yml:
--------------------------------------------------------------------------------
1 | title: until
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Utility Function
10 |
--------------------------------------------------------------------------------
/questions/16-until/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: until
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/17-useCounter/App.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Count: {{ count }}
22 |
23 | inc
24 |
25 |
26 | dec
27 |
28 |
29 | reset
30 |
31 |
32 |
--------------------------------------------------------------------------------
/questions/17-useCounter/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, beforeEach } from "vitest"
3 | import type { DOMWrapper } from "@vue/test-utils"
4 |
5 | import App from "./App.vue"
6 |
7 | async function triggerClick(target: DOMWrapper, times = 1) {
8 | for (let i = 0; i < times; i++)
9 | await target.trigger("click")
10 | }
11 |
12 | describe("useCounter", () => {
13 | let wrapper, incBtn, decBtn, resetBtn, text
14 |
15 | beforeEach(() => {
16 | wrapper = mount(App)
17 | incBtn = wrapper.findAll("button")[0]
18 | decBtn = wrapper.findAll("button")[1]
19 | resetBtn = wrapper.findAll("button")[2]
20 | text = wrapper.find("p")
21 | })
22 |
23 | it("should work", async() => {
24 | expect(text.text()).toBe("Count: 0")
25 | await triggerClick(incBtn, 4)
26 | expect(text.text()).toBe("Count: 4")
27 | await triggerClick(decBtn, 2)
28 | expect(text.text()).toBe("Count: 2")
29 | })
30 |
31 | it("support min and max", async() => {
32 | expect(text.text()).toBe("Count: 0")
33 | await triggerClick(incBtn, 15)
34 | expect(text.text()).toBe("Count: 10")
35 | await triggerClick(decBtn, 20)
36 | expect(text.text()).toBe("Count: 0")
37 | })
38 |
39 | it('support reset', async() => {
40 | expect(text.text()).toBe("Count: 0")
41 | await triggerClick(incBtn, 3)
42 | expect(text.text()).toBe("Count: 3")
43 | await triggerClick(resetBtn)
44 | expect(text.text()).toBe("Count: 0")
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/questions/17-useCounter/info.yml:
--------------------------------------------------------------------------------
1 | title: useCounter
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Composable Function
10 |
--------------------------------------------------------------------------------
/questions/17-useCounter/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 计数器
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/App.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 | Counter: {{ counter }}
28 |
29 | Update
30 |
31 |
32 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/README.md:
--------------------------------------------------------------------------------
1 | useLocalStorage By webfansplz @webfansplz
2 |
3 |
4 | We often need to use the `localStorage` API. A composable function will help us use it better. Lets go. 👇:
5 |
6 |
7 | ```vue
8 |
32 |
33 | ```
34 |
35 |
36 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 实现本地存储函数 By webfansplz @webfansplz
2 |
3 |
4 | 我们经常需要使用`localStorage`API,一个好用的可组合函数封装将帮助我们更好地使用它,让我们开始吧 👇:
5 |
6 |
7 | ```vue
8 |
31 |
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("useLocalStorage", () => {
7 | it("should work", async() => {
8 | let wrapper = mount(App)
9 | expect(wrapper.find("p").text()).toBe("Counter: 0")
10 | await wrapper.find("button").trigger("click")
11 | expect(wrapper.find("p").text()).toBe("Counter: 1")
12 |
13 | wrapper.unmount()
14 |
15 | wrapper = mount(App)
16 | expect(wrapper.find("p").text()).toBe("Counter: 1")
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/info.yml:
--------------------------------------------------------------------------------
1 | title: useLocalStorage
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Composable Function
10 |
--------------------------------------------------------------------------------
/questions/18-useLocalStorage/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 实现本地存储函数
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/19-v-focus/App.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/questions/19-v-focus/README.md:
--------------------------------------------------------------------------------
1 | v-focus By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, we'll start by creating a custom directive. Lets start with `v-focus` 👇:
5 |
6 | ```vue
7 |
27 |
28 |
29 |
30 |
31 |
32 | ```
33 |
34 |
35 |
--------------------------------------------------------------------------------
/questions/19-v-focus/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 切换焦点指令 By webfansplz @webfansplz
2 |
3 |
4 | 这个挑战开始,我们将尝试编写自定义指令,让我们从`v-focus`开始 👇:
5 |
6 | ```vue
7 |
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 |
34 |
--------------------------------------------------------------------------------
/questions/19-v-focus/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("v-focus", () => {
13 | it("should work", async() => {
14 | const wrapper = mount(App, {
15 | attachTo: document.body,
16 | })
17 |
18 | expect(wrapper.find("input").element).not.toBe(document.activeElement)
19 | await delay(3000)
20 | expect(wrapper.find("input").element).toBe(document.activeElement)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/questions/19-v-focus/info.yml:
--------------------------------------------------------------------------------
1 | title: v-focus
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Directives
10 |
--------------------------------------------------------------------------------
/questions/19-v-focus/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 切换焦点指令
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/2-ref-family/App.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
57 | -
58 | {{ count }}
59 | +
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/questions/2-ref-family/index.test.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from "vue"
2 | import { mount } from "@vue/test-utils"
3 | import { describe, it, expect, vi } from "vitest"
4 |
5 | import RefFamily from "./App.vue"
6 |
7 | interface RefFamilyType {
8 | count: Ref
9 | initial: Ref
10 | update: (value: number) => void
11 | initialCount: (value: number | Ref) => number
12 | state: {foo: number; bar: number}
13 | fooRef: Ref
14 | }
15 |
16 | describe("RefFamily", () => {
17 | it("update ref function", () => {
18 | const wrapper = mount(RefFamily);
19 | (wrapper.vm as unknown as RefFamilyType).update(996)
20 | expect((wrapper.vm as unknown as RefFamilyType).count).toBe(996)
21 | })
22 |
23 | it("should work", () => {
24 | const result: string[] = []
25 | console.log = vi.fn((log: string) => {
26 | result.push(log)
27 | })
28 | mount(RefFamily)
29 | expect(JSON.stringify(result)).toBe(JSON.stringify([
30 | 1, true, true, true,
31 | ]))
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/questions/2-ref-family/info.yml:
--------------------------------------------------------------------------------
1 | title: ref family
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Reactivity:Core
10 |
--------------------------------------------------------------------------------
/questions/2-ref-family/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: ref 全家桶
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/20-v-debounce-click/App.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 | Click on it many times quickly
23 |
24 |
25 |
--------------------------------------------------------------------------------
/questions/20-v-debounce-click/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 防抖点击指令 By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,我们将尝试实现一个防抖点击指令,让我们开始吧 👇:
5 |
6 | ```vue
7 |
25 |
26 |
27 |
28 | Click on it many times quickly
29 |
30 |
31 |
32 | ```
33 |
34 |
35 |
--------------------------------------------------------------------------------
/questions/20-v-debounce-click/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 | import type { DOMWrapper } from "@vue/test-utils"
4 |
5 | import App from "./App.vue"
6 |
7 | async function triggerClick(target: DOMWrapper, times = 1) {
8 | for (let i = 0; i < times; i++)
9 | await target.trigger("click")
10 | }
11 |
12 | describe("v-debounce-click", () => {
13 | it("should work", async() => {
14 | const result: string[] = []
15 | console.log = vi.fn((log: string) => {
16 | result.push(log)
17 | })
18 | const wrapper = mount(App)
19 | await triggerClick(wrapper.find("button"), 2)
20 | expect(JSON.stringify(result)).toBe("[\"Only triggered once when clicked many times quickly\"]")
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/questions/20-v-debounce-click/info.yml:
--------------------------------------------------------------------------------
1 | title: v-debounce-click
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Directives
10 |
--------------------------------------------------------------------------------
/questions/20-v-debounce-click/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 防抖点击指令
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/208-tree-component/App.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/questions/208-tree-component/README.md:
--------------------------------------------------------------------------------
1 | Tree Component By 木荣 @murongg
2 |
3 | For this challenge, you need to implement a tree component. Lets go.
4 |
5 | ```vue
6 |
14 |
15 |
16 |
17 |
18 | ```
19 |
20 |
21 |
--------------------------------------------------------------------------------
/questions/208-tree-component/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 树组件 By 木荣 @murongg
2 |
3 |
4 | 在这个挑战中,你需要实现一个树组件,让我们开始吧。
5 |
6 | ```vue
7 |
15 |
16 |
17 |
18 |
19 | ```
20 |
21 |
22 |
--------------------------------------------------------------------------------
/questions/208-tree-component/TreeComponent.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/questions/208-tree-component/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 | import Tree from "./TreeComponent.vue"
6 |
7 | describe("TreeComponent", () => {
8 | it("should work", async() => {
9 | const wrapper = mount(App)
10 | const wrapperTree = wrapper.findComponent(Tree)
11 | await wrapper.setProps({
12 | data: [
13 | {
14 | key: "1",
15 | title: "Parent 1",
16 | children: [{
17 | key: "1-1",
18 | title: "child 1",
19 | }, {
20 | key: "1-2",
21 | title: "child 2",
22 | children: [{
23 | key: "1-2-1",
24 | title: "grandchild 1",
25 | }, {
26 | key: "1-2-2",
27 | title: "grandchild 2",
28 | }],
29 | }],
30 | },
31 | {
32 | key: "2",
33 | title: "Parent 2",
34 | children: [{
35 | key: "2-1",
36 | title: "child 1",
37 | children: [{
38 | key: "2-1-1",
39 | title: "grandchild 1",
40 | }, {
41 | key: "2-1-2",
42 | title: "grandchild 2",
43 | }],
44 | }, {
45 | key: "2-2",
46 | title: "child 2",
47 | }],
48 | },
49 | {
50 | key: "3",
51 | title: "Parent 3",
52 | children: [{
53 | key: "3-1",
54 | title: "child 1",
55 | children: [{
56 | key: "3-1-1",
57 | title: "grandchild 1",
58 | }, {
59 | key: "3-1-2",
60 | title: "grandchild 2",
61 | }],
62 | }],
63 | },
64 | ],
65 | })
66 | expect(wrapperTree.text()).toMatchInlineSnapshot("\"Parent 1child 1child 2grandchild 1grandchild 2Parent 2child 1grandchild 1grandchild 2child 2Parent 3child 1grandchild 1grandchild 2\"")
67 | })
68 | })
69 |
--------------------------------------------------------------------------------
/questions/208-tree-component/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: hard
2 | title: Tree Component
3 | tags: Components
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/208-tree-component/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: hard
2 | title: 树组件
3 | tags: Components
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/21-functional-component/App.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
35 |
36 |
--------------------------------------------------------------------------------
/questions/21-functional-component/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("functional-component", () => {
7 | it("should work", async() => {
8 | const wrapper = mount(App)
9 | const list = wrapper.findComponent({ name: "list-component" })
10 | expect(list.vm).toBeUndefined()
11 |
12 | const item1 = list.findAll("li")[0]
13 | const item2 = list.findAll("li")[1]
14 | expect(item1.attributes("style")).toBe("color: red;")
15 | expect(item2.attributes("style")).toBeUndefined()
16 |
17 | await item2.trigger("click")
18 |
19 | expect(item1.attributes("style")).toBeUndefined()
20 | expect(item2.attributes("style")).toBe("color: red;")
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/questions/21-functional-component/info.yml:
--------------------------------------------------------------------------------
1 | title: functional component
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Components
10 |
--------------------------------------------------------------------------------
/questions/21-functional-component/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 函数式组件
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/App.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 | my button
13 |
14 |
15 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/MyButton.ts:
--------------------------------------------------------------------------------
1 | import { defineComponent } from "vue"
2 |
3 | export default defineComponent({
4 | name: 'MyButton',
5 | render() {
6 | return h(/** do someting */)
7 | }
8 | })
--------------------------------------------------------------------------------
/questions/218-h-render-function/README.md:
--------------------------------------------------------------------------------
1 | render function[h()] By 木荣 @murongg
2 |
3 |
4 | For this challenge, you need use `h` render function to implement a component.
5 |
6 | Note: You should make sure that the props are passed correctly, event is triggered correctly and the slot content is rendered correctly. Lets go.
7 |
8 | ```vue
9 |
15 |
16 |
17 |
18 | my button
19 |
20 |
21 |
22 | ```
23 |
24 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 渲染函数[h()] By 木荣 @murongg
2 |
3 | 在这个挑战中,你需要使用`h`渲染函数来实现一个组件。
4 |
5 | 请注意: 你应该确保参数被正确传递、事件被正常触发和插槽内容正常渲染。让我们开始吧。
6 |
7 | ```vue
8 |
14 |
15 |
16 | my button
17 |
18 |
19 | ```
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import MyButton from "./MyButton"
5 |
6 | describe("Render function h()", () => {
7 | it("renders a 'MyButton'", () => {
8 | const wrapper = mount(MyButton)
9 | expect(wrapper.element.tagName.toLocaleLowerCase()).toBe("button")
10 | })
11 |
12 | it("disabled", async() => {
13 | const wrapper = mount(MyButton, {
14 | props: {
15 | disabled: true,
16 | },
17 | })
18 | expect(wrapper.find("button").attributes()).toBeDefined()
19 |
20 | await wrapper.trigger("click")
21 | expect(wrapper.emitted("click")).toBeUndefined()
22 | })
23 |
24 | it("slot", () => {
25 | const wrapper = mount(MyButton, {
26 | slots: {
27 | default: "my button",
28 | },
29 | })
30 | expect(wrapper.text()).toBe("my button")
31 | })
32 |
33 | it("custom click defined", () => {
34 | const wrapper = mount(MyButton)
35 | wrapper.trigger("click")
36 | expect(wrapper.emitted().customClick || wrapper.emitted()["custom-click"]).toBeTruthy()
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: medium
2 | title: render function[h()]
3 | tags: Components
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/218-h-render-function/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: medium
2 | title: 渲染函数[h()]
3 | tags: Components
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/22-custom-element/App.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/questions/22-custom-element/README.md:
--------------------------------------------------------------------------------
1 | custom element By webfansplz @webfansplz
2 |
3 |
4 | Have you heard about `Web Components` ?
5 |
6 | Vue has excellent support for both creating and consuming custom elements.
7 |
8 | For this challenge, you can try it out. Lets go 👇:
9 |
10 | ```vue
11 |
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 |
34 |
--------------------------------------------------------------------------------
/questions/22-custom-element/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 自定义元素 By webfansplz @webfansplz
2 |
3 |
4 | 你听说过 `Web Components` 吗 ?
5 |
6 | Vue 能很好地解析和创建 `Web Components` 。
7 |
8 | 在这个挑战中,我们将尝试了解它,让我们开始吧 👇:
9 |
10 | ```vue
11 |
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/questions/22-custom-element/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("custom-component", () => {
7 | it("should work", () => {
8 | const wrapper = mount(App, { attachTo: document.body })
9 | const custom = wrapper.find('vue-js')
10 | expect(custom.exists()).toBeTruthy()
11 | expect(custom.element.shadowRoot?.innerHTML).toBe("Hello Vue.js ")
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/questions/22-custom-element/info.yml:
--------------------------------------------------------------------------------
1 | title: custom element
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: hard
8 |
9 | tags: Web Components
10 |
--------------------------------------------------------------------------------
/questions/22-custom-element/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 自定义元素
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/23-custom-ref/App.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/questions/23-custom-ref/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 自定义ref By webfansplz @webfansplz
2 |
3 |
4 | 防抖函数在输入框操作场景中非常有用。
5 |
6 | 一个 防抖的`ref`在`Vue.js`更加灵活,让我们开始吧 👇:
7 |
8 | ```vue
9 |
27 |
28 |
29 |
30 |
31 |
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/questions/23-custom-ref/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("custom-ref", () => {
13 | it("should work", async() => {
14 | let printLog = ''
15 | console.log = vi.fn((log: string) => {
16 | printLog = log?.toString()?.trim()
17 | })
18 |
19 | const wrapper = mount(App)
20 |
21 | await wrapper.find("input").setValue("hello!")
22 | expect(printLog).toMatchInlineSnapshot('""')
23 | await delay(200)
24 | expect(printLog).toMatchInlineSnapshot('"hello!"')
25 |
26 | await wrapper.find("input").setValue("world")
27 | expect(printLog).toMatchInlineSnapshot('"hello!"')
28 | await delay(200)
29 | expect(printLog).toMatchInlineSnapshot('"world"')
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/questions/23-custom-ref/info.yml:
--------------------------------------------------------------------------------
1 | title: custom ref
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: hard
8 |
9 | tags: Composition API,Reactivity:Advanced
10 |
--------------------------------------------------------------------------------
/questions/23-custom-ref/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 自定义ref
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/232-key-modifiers/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | A
4 |
5 |
6 | A
7 |
8 |
9 | A
10 |
11 |
12 |
--------------------------------------------------------------------------------
/questions/232-key-modifiers/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 按键修饰符 By webfansplz @webfansplz
2 |
3 |
4 | 在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符:,例如:
5 |
6 | ```vue
7 |
8 |
9 | ```
10 |
11 | 在这个挑战中,我们将尝试它,让我们开始吧:
12 |
13 | ```vue
14 |
15 |
16 | A
17 |
18 |
19 | A
20 |
21 |
22 | A
23 |
24 |
25 | ```
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/questions/232-key-modifiers/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("key modifiers", () => {
7 | it("should work", async() => {
8 | let printLog = ""
9 | console.log = vi.fn(
10 | (log: string) => {
11 | printLog = log?.toString()?.trim()
12 | })
13 | const wrapper = mount(App)
14 | const buttons = wrapper.findAll("button")
15 |
16 | await buttons[0].trigger('click')
17 | expect(printLog).toMatchInlineSnapshot('""')
18 | await buttons[0].trigger('click.alt')
19 | expect(printLog).toMatchInlineSnapshot('"onClick1"')
20 | await buttons[0].trigger('click.shift')
21 | expect(printLog).toMatchInlineSnapshot('"onClick1"')
22 |
23 | await buttons[1].trigger('click')
24 | expect(printLog).toMatchInlineSnapshot('"onClick1"')
25 | await buttons[1].trigger('click.shift')
26 | expect(printLog).toMatchInlineSnapshot('"onCtrlClick"')
27 |
28 | await buttons[2].trigger('click')
29 | expect(printLog).toMatchInlineSnapshot('"onClick2"')
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/questions/232-key-modifiers/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: medium
2 | title: Key Modifiers
3 | tags: Event Handling
4 | author:
5 | github: webfansplz
6 | name: webfansplz
7 |
8 |
--------------------------------------------------------------------------------
/questions/232-key-modifiers/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: medium
2 | title: 按键修饰符
3 | tags: Event Handling
4 | author:
5 | github: webfansplz
6 | name: webfansplz
7 |
8 |
--------------------------------------------------------------------------------
/questions/24-v-active-style/App.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
30 | {{ item }}
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/questions/24-v-active-style/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("v-active-style", () => {
7 | it("should work", async() => {
8 | const wrapper = mount(App)
9 | const list = wrapper.findAll('li')
10 |
11 | expect(list[0].attributes("style")).toBe("color: red;")
12 | expect(list[1].attributes("style")).toBeUndefined()
13 |
14 | await list[1].trigger("click")
15 |
16 | expect(list[0].attributes("style")).toBe("")
17 | expect(list[1].attributes("style")).toBe("color: red;")
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/questions/24-v-active-style/info.yml:
--------------------------------------------------------------------------------
1 | title: v-active-style
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: hard
8 |
9 | tags: Directives
10 |
--------------------------------------------------------------------------------
/questions/24-v-active-style/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 激活的样式-指令
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/App.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | click me
17 |
18 |
19 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/README.md:
--------------------------------------------------------------------------------
1 | prevent event propagation By 木荣 @murongg
2 |
3 |
4 | In this challenge,you should make the click event's propagation to be stopped,let's go 👇:
5 |
6 |
7 | ```vue
8 |
19 |
20 |
21 |
22 |
23 | click me
24 |
25 |
26 |
27 | ```
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 阻止事件冒泡 By 木荣 @murongg
2 |
3 |
4 | 在这个挑战中,你需要阻止点击事件的冒泡,让我们开始吧。
5 |
6 | ```vue
7 |
18 |
19 |
20 |
21 |
22 | click me
23 |
24 |
25 |
26 | ```
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("prevent-event-propagation", () => {
7 | it("should work", async() => {
8 | const result: string[] = []
9 | console.log = vi.fn((log: string) => {
10 | result.push(log)
11 | })
12 |
13 | const wrapper = mount(App)
14 |
15 | await wrapper.findAll("div")[1].trigger("click")
16 | expect(JSON.stringify(result)).toBe("[\"click2\"]")
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: prevent event propagation
3 | tags: Event Handling
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/243-prevent-event-propagation/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: 阻止事件冒泡
3 | tags: Event Handling
4 | author:
5 | github: murongg
6 | name: 木荣
7 |
8 |
--------------------------------------------------------------------------------
/questions/25-useMouse/App.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 | Mouse position is at: {{ x }}, {{ y }}
16 |
--------------------------------------------------------------------------------
/questions/25-useMouse/README.md:
--------------------------------------------------------------------------------
1 | useMouse By webfansplz @webfansplz
2 |
3 |
4 | We should focus on reusability when using `Vue.js`. Composables are a great way to ensure this. Let's go 👇:
5 |
6 |
7 | ```vue
8 |
21 |
22 | Mouse position is at: {{ x }}, {{ y }}
23 |
24 |
25 | ```
26 |
27 |
28 |
--------------------------------------------------------------------------------
/questions/25-useMouse/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 鼠标坐标 By webfansplz @webfansplz
2 |
3 |
4 | 在使用`Vue.js`时,我们应该关注可复用性,可组合函数是一个很好的方式,让我们开始吧 👇:
5 |
6 |
7 | ```vue
8 |
21 |
22 | Mouse position is at: {{ x }}, {{ y }}
23 |
24 |
25 | ```
26 |
27 |
--------------------------------------------------------------------------------
/questions/25-useMouse/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 | import { nextTick } from "vue"
4 |
5 | import App from "./App.vue"
6 |
7 | describe("useMouse", () => {
8 | it("should work", async() => {
9 | const wrapper = mount(App)
10 | expect(wrapper.html()).toBe("Mouse position is at: 0, 0")
11 |
12 | const mousemove = new MouseEvent("mousemove", {
13 | screenX: 10,
14 | screenY: 20,
15 | clientX: 10,
16 | clientY: 20,
17 | })
18 | window.dispatchEvent(mousemove)
19 |
20 | await nextTick()
21 | expect(wrapper.html()).toBe("Mouse position is at: 10, 20")
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/questions/25-useMouse/info.yml:
--------------------------------------------------------------------------------
1 | title: useMouse
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Composable Function
10 |
--------------------------------------------------------------------------------
/questions/25-useMouse/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 鼠标坐标
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/26-v-model/App.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {{ value }}
21 |
22 |
--------------------------------------------------------------------------------
/questions/26-v-model/README.md:
--------------------------------------------------------------------------------
1 | v-model By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, we're going to implement a simple `v-model` directive. Lets go 👇:
5 |
6 | ```vue
7 |
23 |
24 |
25 |
26 |
27 |
28 | ```
29 |
30 |
31 |
--------------------------------------------------------------------------------
/questions/26-v-model/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 实现简易版`v-model`指令 By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,我们将尝试实现一个简单的`v-model`指令,让我们开始吧 👇:
5 |
6 | ```vue
7 |
23 |
24 |
25 |
26 |
27 |
28 | ```
29 |
30 |
--------------------------------------------------------------------------------
/questions/26-v-model/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("v-model", () => {
7 | it("should work", async() => {
8 | const wrapper = mount(App)
9 | const input = wrapper.find("input")
10 | const p = wrapper.find("p")
11 |
12 | expect(input.element.value).toBe("Hello Vue.js")
13 | expect(p.text()).toBe(input.element.value)
14 |
15 | await input.setValue("Hello World")
16 | expect(input.element.value).toBe("Hello World")
17 | expect(p.text()).toBe(input.element.value)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/questions/26-v-model/info.yml:
--------------------------------------------------------------------------------
1 | title: v-model
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: hard
8 |
9 | tags: Directives
10 |
--------------------------------------------------------------------------------
/questions/26-v-model/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 实现简易版`v-model`指令
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/27-global-css/App.vue:
--------------------------------------------------------------------------------
1 |
2 | Hello Vue.js
3 |
4 |
5 |
21 |
--------------------------------------------------------------------------------
/questions/27-global-css/README.md:
--------------------------------------------------------------------------------
1 | Global CSS By webfansplz @webfansplz
2 |
3 |
4 | Sometimes, we may want to set global CSS in the scoped component. Do you know how to solve it?. Lets go 👇:
5 |
6 | ```css
7 |
8 | Hello Vue.js
9 |
10 |
11 |
27 | ```
28 |
29 |
30 |
--------------------------------------------------------------------------------
/questions/27-global-css/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 全局CSS By webfansplz @webfansplz
2 |
3 |
4 | 有些时候,我们想在具有CSS作用域的`Vue`单文件组件设置全局CSS样式, 该怎么设置呢 ? 让我们开始吧 👇:
5 |
6 | ```css
7 |
8 | Hello Vue.js
9 |
10 |
11 |
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/questions/27-global-css/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest"
2 |
3 | import AppRaw from "./App.vue?raw"
4 |
5 | describe("DomPortal", () => {
6 | it("render to body", () => {
7 | expect(AppRaw).toContain(atob("Omdsb2JhbChib2R5KQ=="))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/questions/27-global-css/info.yml:
--------------------------------------------------------------------------------
1 | title: Global CSS
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: CSS Features
10 |
--------------------------------------------------------------------------------
/questions/27-global-css/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 全局CSS
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/3-losing-reactivity/App.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 | -
28 | {{ count }}
29 | +
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/questions/3-losing-reactivity/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 | import type { Ref } from "vue"
4 | import { isRef } from "vue"
5 |
6 | import LosingReactivity from "./App.vue"
7 |
8 | interface LosingReactivityType {
9 | useCount: () => {
10 | state: {count: Ref}
11 | update: (value: number) => void
12 | }
13 | }
14 |
15 | describe("LosingReactivity", () => {
16 | it("count is Ref", () => {
17 | const wrapper = mount(LosingReactivity)
18 | const { state: { count } } = (wrapper.vm as unknown as LosingReactivityType).useCount()
19 | expect(isRef(count)).toBe(true)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/questions/3-losing-reactivity/info.yml:
--------------------------------------------------------------------------------
1 | title: losing-reactivity
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Reactivity:Utilities
10 |
--------------------------------------------------------------------------------
/questions/3-losing-reactivity/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 响应性丟失
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/305-capitalize/App.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/questions/305-capitalize/README.md:
--------------------------------------------------------------------------------
1 | Capitalize By Lov`u`e @heappynd
2 |
3 | Create a custom modifier for the 'v-model' directive that changes the first letter of the 'v-model' binding value to uppercase.
4 |
5 | ```vue
6 |
8 |
9 |
10 |
11 |
12 | ```
13 |
14 |
15 |
--------------------------------------------------------------------------------
/questions/305-capitalize/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 大写 By Lov`u`e @heappynd
2 |
3 | 请创建一个自定义的修饰符 `capitalize`,它会自动将 `v-model` 绑定输入的字符串值首字母转为大写:
4 | ```vue
5 |
7 |
8 |
9 |
10 |
11 | ```
12 |
13 |
14 |
--------------------------------------------------------------------------------
/questions/305-capitalize/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("capitalize", () => {
7 | it("should work", async() => {
8 | const wrapper = mount(App)
9 | await wrapper.find('input').setValue("hello")
10 | expect(wrapper.find('input').element.value).toBe("Hello")
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/questions/305-capitalize/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: Capitalize
3 | tags: Directives
4 | author:
5 | github: heappynd
6 | name: Lov`u`e
7 |
8 |
--------------------------------------------------------------------------------
/questions/305-capitalize/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: 大写
3 | tags: Directives
4 | author:
5 | github: heappynd
6 | name: Lov`u`e
7 |
8 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/App.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/Button.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Button
9 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/README.md:
--------------------------------------------------------------------------------
1 | Prop Validation By Lov`u`e @heappynd
2 |
3 |
4 | Please validate the `type` prop of the `Button` component. it accepts the following strings `primary | ghost | dashed | link | text | default` only and the default value is `default`.
5 |
6 | ```vue
7 |
12 |
13 |
14 | Button
15 |
16 | ```
17 |
18 |
19 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | Prop验证 By Lov`u`e @heappynd
2 |
3 |
4 | 请验证`Button`组件的`Prop`类型 ,使它只接收: `primary | ghost | dashed | link | text | default` ,且默认值为`default`。
5 |
6 | ```vue
7 |
12 |
13 |
14 | Button
15 |
16 | ```
17 |
18 |
19 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import Button from "./Button.vue"
5 |
6 | describe("prop-validation", () => {
7 | it("should have a default type prop of 'default'", () => {
8 | const wrapper = mount(Button)
9 | expect(wrapper.vm.type).toBe("default")
10 | })
11 | it("should only accept specific values for the type prop", () => {
12 | const wrapper = mount(Button, {
13 | })
14 | expect(wrapper.vm.$options.props.type.validator("invalid")).toBe(false)
15 | const validTypes = ["primary", "ghost", "dashed", "link", "text", "default"]
16 | const randomType = validTypes[Math.floor(Math.random() * validTypes.length)]
17 | expect(wrapper.vm.$options.props.type.validator(randomType)).toBe(true)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/info.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: Prop Validation
3 | tags: Components
4 | author:
5 | github: heappynd
6 | name: Lov`u`e
7 |
8 |
--------------------------------------------------------------------------------
/questions/323-prop-validation/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | difficulty: easy
2 | title: Prop验证
3 | tags: Components
4 | author:
5 | github: heappynd
6 | name: Lov`u`e
7 |
8 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/App.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
{{ count }}
19 |
{{ plusOne }}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/README.md:
--------------------------------------------------------------------------------
1 | writable-computed By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, you will need to create a writable computed ref :
5 |
6 | ```vue
7 |
21 |
22 |
23 |
24 |
{{ count }}
25 |
{{ plusOne }}
26 |
27 |
28 |
29 | ```
30 |
31 |
32 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 可写的计算属性 By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,你需要创建一个可写的计算属性 :
5 |
6 | ```vue
7 |
21 |
22 |
23 |
24 |
{{ count }}
25 |
{{ plusOne }}
26 |
27 |
28 |
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import Writable from "./App.vue"
5 |
6 | interface WritableType {
7 | plusOne: number
8 | count: number
9 | }
10 |
11 | describe("Writable", () => {
12 | it("Make the `plusOne` writable", () => {
13 | const wrapper = mount(Writable)
14 | expect((wrapper.vm as unknown as WritableType).plusOne).toBe(3)
15 | expect((wrapper.vm as unknown as WritableType).count).toBe(2)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/info.yml:
--------------------------------------------------------------------------------
1 | title: writable-computed
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Reactivity:Core
10 |
--------------------------------------------------------------------------------
/questions/4-writable-computed/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 可写的计算属性
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/5-watch-family/App.vue:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
47 |
48 | {{ count }}
49 |
50 |
51 | {{ age }}
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/questions/5-watch-family/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("Watch Faimily", () => {
13 | it("should work", async() => {
14 | const result: string[] = []
15 | console.log = vi.fn((log: string) => {
16 | result.push(log?.toString()?.trim())
17 | })
18 | mount(App)
19 | await delay(1000)
20 | expect(JSON.stringify(result)).toBe(JSON.stringify([
21 | "Only triggered once",
22 | "The state.count updated",
23 | "[object HTMLParagraphElement]",
24 | ]))
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/questions/5-watch-family/info.yml:
--------------------------------------------------------------------------------
1 | title: watch family
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Reactivity:Core
10 |
11 |
--------------------------------------------------------------------------------
/questions/5-watch-family/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: watch 全家桶
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/App.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 | {{ state.count }}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/README.md:
--------------------------------------------------------------------------------
1 | shallow ref By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, you'll use `Reactivity API: shallowRef` to complete the challenge.
5 | Here's what you need to implement 👇:
6 |
7 | ```vue
8 |
24 |
25 |
26 |
27 |
28 | {{ state.count }}
29 |
30 |
31 |
32 |
33 | ```
34 |
35 |
36 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 浅层 ref By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,你将使用 `响应式 API: shallowRef` 来完成它。
5 | 以下是你要实现的内容 👇:
6 |
7 | ```vue
8 |
25 |
26 |
27 |
28 |
29 | {{ state.count }}
30 |
31 |
32 |
33 |
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("shallowRef", () => {
7 | it("should work", () => {
8 | const spy = vi.spyOn(console, "log")
9 | mount(App)
10 | expect(spy).toHaveBeenCalled()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/info.yml:
--------------------------------------------------------------------------------
1 | title: shallow ref
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API,Reactivity:Advanced
10 |
--------------------------------------------------------------------------------
/questions/6-shallow-ref/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 浅层 ref
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/7-raw-api/App.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 | {{ reactiveState.count }}
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/questions/7-raw-api/README.md:
--------------------------------------------------------------------------------
1 | Raw API By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, you'll use `Reactivity API: [xx]Raw` to complete the challenge.
5 | Here's what you need to implement 👇:
6 |
7 | ```vue
8 |
28 |
29 |
30 |
31 |
32 | {{ reactiveState.count }}
33 |
34 |
35 |
36 |
37 |
38 | ```
39 |
40 |
41 |
--------------------------------------------------------------------------------
/questions/7-raw-api/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 原始值 API By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,你将使用 `响应式 API: [xx]Raw` 来完成它。
5 | 以下是你要实现的内容 👇:
6 |
7 | ```vue
8 |
28 |
29 |
30 |
31 |
32 | {{ reactiveState.count }}
33 |
34 |
35 |
36 |
37 |
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/questions/7-raw-api/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("raw API", () => {
7 | it("should work", async() => {
8 | const result: string[] = []
9 | console.log = vi.fn((log: string) => {
10 | result.push(log)
11 | })
12 | mount(App)
13 | expect(JSON.stringify(result)).toBe(JSON.stringify([
14 | true,
15 | false,
16 | ]))
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/questions/7-raw-api/info.yml:
--------------------------------------------------------------------------------
1 | title: Raw API
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Reactivity:Advanced
10 |
--------------------------------------------------------------------------------
/questions/7-raw-api/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 原始值 API
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/8-effect-scope/App.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 | {{ doubled }}
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/questions/8-effect-scope/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect, vi } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | function delay(timeout: number) {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout)
9 | })
10 | }
11 |
12 | describe("EffectScope", () => {
13 | it("should work", async() => {
14 | const result: string[] = []
15 | console.log = vi.fn((log: string) => {
16 | result.push(log)
17 | })
18 | mount(App)
19 | await delay(1000)
20 | expect(JSON.stringify(result)).toBe(JSON.stringify([
21 | "Count: 2", 4, "Count: 4",
22 | ]))
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/questions/8-effect-scope/info.yml:
--------------------------------------------------------------------------------
1 | title: effectScope API
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: medium
8 |
9 | tags: Composition API,Reactivity:Advanced
10 |
--------------------------------------------------------------------------------
/questions/8-effect-scope/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: Effect作用域 API
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/App.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/Child.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | {{ count }}
7 |
8 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/README.md:
--------------------------------------------------------------------------------
1 | Dependency Injection By webfansplz @webfansplz
2 |
3 |
4 | For this challenge, you'll use the `Composition API: Dependency Injection` to complete the challenge.
5 | Here's what you need to implement 👇:
6 |
7 | ```vue
8 | // Child.vue
9 |
10 |
13 |
14 |
15 | {{ count }}
16 |
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | 依赖注入 By webfansplz @webfansplz
2 |
3 |
4 | 在这个挑战中,你将使用 `组合式 API: 依赖注入` 来完成它。
5 | 以下是你要实现的内容 👇:
6 |
7 | ```vue
8 | // Child.vue
9 |
10 |
13 |
14 |
15 | {{ count }}
16 |
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/index.test.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import { describe, it, expect } from "vitest"
3 |
4 | import App from "./App.vue"
5 |
6 | describe("Dependency Injection", () => {
7 | it("should work'", () => {
8 | const wrapper = mount(App)
9 | expect(wrapper.vm.$el.textContent).toBe("1")
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/info.yml:
--------------------------------------------------------------------------------
1 | title: Dependency Injection
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
7 | difficulty: easy
8 |
9 | tags: Composition API
10 |
--------------------------------------------------------------------------------
/questions/9-dependency-injection/info.zh-CN.yml:
--------------------------------------------------------------------------------
1 | title: 依赖注入
2 |
3 | author:
4 | name: webfansplz
5 | github: webfansplz
6 |
--------------------------------------------------------------------------------
/scripts/actions/labeling.ts:
--------------------------------------------------------------------------------
1 | import { Action } from "../types"
2 |
3 | const action: Action = async(github, context, core) => {
4 | const payload = context.payload
5 | const issue = payload.issue
6 |
7 | if (!issue)
8 | return
9 |
10 | const labels: string[] = (issue.labels || [])
11 | .map((i: any) => i && i.name)
12 | .filter(Boolean)
13 |
14 | if (labels.includes("answer")) {
15 | const match = issue.title.match(/^(\d+) - /)
16 | if (match && match[1]) {
17 | const no = Number(match[1])
18 | if (isNaN(no))
19 | return
20 |
21 | const name = no.toString()
22 |
23 | if (labels.includes("trigger-bot")) {
24 | await github.issues.removeLabel({
25 | issue_number: context.issue.number,
26 | owner: context.repo.owner,
27 | repo: context.repo.repo,
28 | name: "trigger-bot",
29 | })
30 | }
31 |
32 | if (labels.includes(name))
33 | return
34 |
35 | try {
36 | await github.issues.getLabel({
37 | owner: context.repo.owner,
38 | repo: context.repo.repo,
39 | name,
40 | })
41 | }
42 | catch {
43 | await github.issues.createLabel({
44 | owner: context.repo.owner,
45 | repo: context.repo.repo,
46 | name,
47 | color: "ffffff",
48 | })
49 | }
50 |
51 | await github.issues.addLabels({
52 | issue_number: context.issue.number,
53 | owner: context.repo.owner,
54 | repo: context.repo.repo,
55 | labels: [name],
56 | })
57 | }
58 | }
59 | else {
60 | core.info("No matched labels, skipped")
61 | }
62 | }
63 |
64 | export default action
65 |
--------------------------------------------------------------------------------
/scripts/actions/loader.ts:
--------------------------------------------------------------------------------
1 | import * as core from "@actions/core"
2 | import { context, getOctokit } from "@actions/github"
3 | import * as io from "@actions/io"
4 |
5 | process.on("unhandledRejection", handleError)
6 | main().catch(handleError)
7 |
8 | async function main(): Promise {
9 | const token = process.argv[2]
10 | const fnName = process.argv[3]
11 | const github = getOctokit(token)
12 |
13 | const fn = require(`./${fnName}.ts`)
14 | fn.default(github, context, core, io)
15 | }
16 |
17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
18 | function handleError(err: any): void {
19 | console.error(err)
20 | core.setFailed(`Unhandled error: ${err}`)
21 | process.exit(1)
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/actions/toggle-pr-with-issue.ts:
--------------------------------------------------------------------------------
1 | import { Action } from "../types"
2 |
3 | const action: Action = async(github, context, core) => {
4 | const payload = context.payload || {}
5 | const issue = payload.issue
6 |
7 | if (!issue)
8 | return
9 |
10 | const labels: string[] = (issue.labels || [])
11 | .map((i: any) => i && i.name)
12 | .filter(Boolean)
13 |
14 | if (!labels.includes("new-challenge"))
15 | return
16 |
17 | // close pull request
18 | // Leave a message: close by issue
19 | const no = issue.number
20 | const action = payload.action
21 |
22 | core.info(`action: ${action}`)
23 |
24 | // action: reopened
25 | // action: closed
26 |
27 | // find pull request
28 | const { data: pulls } = await github.pulls.list({
29 | owner: context.repo.owner,
30 | repo: context.repo.repo,
31 | state: action === "closed" ? "open" : "closed",
32 | })
33 |
34 | core.info(`pulls.length ${pulls.length}`)
35 | core.info(JSON.stringify(pulls))
36 |
37 | const existing_pull = pulls.find(i =>
38 | i.user.login === "github-actions[bot]"
39 | && i.title.startsWith(`#${no} `),
40 | )
41 |
42 | if (!existing_pull) {
43 | core.info("existing_pull not exist")
44 | return
45 | }
46 |
47 | core.info(JSON.stringify(context))
48 |
49 | if (context.payload.action === "reopened") {
50 | await github.pulls.update({
51 | ...context.repo,
52 | pull_number: existing_pull.number,
53 | state: "open",
54 | })
55 | }
56 | else {
57 | // close
58 | await github.pulls.update({
59 | ...context.repo,
60 | pull_number: existing_pull.number,
61 | state: "closed",
62 | })
63 | }
64 | }
65 |
66 | export default action
67 |
--------------------------------------------------------------------------------
/scripts/badge.ts:
--------------------------------------------------------------------------------
1 | import { SupportedLocale, t } from "./locales"
2 | import { DIFFICULTY_COLORS } from "./configs"
3 | export function generateBadgeURL(label: string, text: string, color: string, args = "") {
4 | return `https://img.shields.io/badge/${encodeURIComponent(label.replace(/-/g, "--"))}-${encodeURIComponent(text.replace(/-/g, "--"))}-${color}${args}`
5 | }
6 |
7 | export function generateBadge(label: string, text: string, color: string, args = "") {
8 | return ` `
9 | }
10 |
11 | export function generateBadgeLink(url: string, label: string, text: string, color: string, args = "") {
12 | return `${generateBadge(label, text, color, args)} `
13 | }
14 |
15 | export function generateDifficultyBadge(difficulty: string, locale: SupportedLocale) {
16 | return generateBadge("", t(locale, `difficulty.${difficulty}`), DIFFICULTY_COLORS[difficulty])
17 | }
18 |
19 | export function generateDifficultyBadgeInverted(difficulty: string, locale: SupportedLocale, count: number) {
20 | return generateBadge(t(locale, `difficulty.${difficulty}`), count.toString(), DIFFICULTY_COLORS[difficulty])
21 | }
22 |
--------------------------------------------------------------------------------
/scripts/build.ts:
--------------------------------------------------------------------------------
1 |
2 | import { loadQuizes } from "./loader"
3 | import { updateQuizREADME, updateIndexREADME } from "./readme"
4 |
5 | export async function build() {
6 | const quizes = await loadQuizes()
7 | quizes.sort((a, b) => a.no - b.no)
8 | await updateQuizREADME(quizes)
9 | await updateIndexREADME(quizes)
10 | }
11 |
12 | build()
13 |
--------------------------------------------------------------------------------
/scripts/configs.ts:
--------------------------------------------------------------------------------
1 |
2 | import path from "path"
3 | export const QUIZ_ROOT = path.resolve(__dirname, "../questions")
4 | export const VUE_SFC_PLAYGROUND_URL = "https://sfc.vuejs.org/"
5 | export const STACKBLITZ_PLAYGROUND_URL = "https://vuejs-challenges-stackblitz.netlify.app/"
6 | export const REPO = "https://github.com/webfansplz/vuejs-challenges"
7 |
8 | export const DIFFICULTY_COLORS: Record = {
9 | warm: "teal",
10 | easy: "7aad0c",
11 | medium: "d9901a",
12 | hard: "de3d37",
13 | extreme: "b11b8d",
14 | }
15 |
16 | export const DIFFICULTY_RANK = [
17 | "warm",
18 | "easy",
19 | "medium",
20 | "hard",
21 | "extreme",
22 | ]
23 |
--------------------------------------------------------------------------------
/scripts/locales.ts:
--------------------------------------------------------------------------------
1 | import en from "./locales/en.json"
2 | import zhCN from "./locales/zh-CN.json"
3 |
4 | export const defaultLocale = "en"
5 |
6 | export const supportedLocales = ["en", "zh-CN"] as const
7 |
8 | export const messages = {
9 | "en": en,
10 | "zh-CN": zhCN,
11 | }
12 |
13 | export type SupportedLocale = keyof typeof messages
14 |
15 | export function t(locale: SupportedLocale, key: string): string {
16 | return (messages[locale] && messages[locale][key]) || messages[defaultLocale][key]
17 | }
18 |
19 | export function f(name: string, locale: string, ext: string) {
20 | if (locale === defaultLocale)
21 | return `${name}.${ext}`
22 | return `${name}.${locale}.${ext}`
23 | }
24 |
--------------------------------------------------------------------------------
/scripts/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "badge.back": "Back",
3 | "badge.checkout-solutions": "Check out Solutions",
4 | "badge.preview-playground": "Preview in Playground",
5 | "badge.share-your-solutions": "Share your Solutions",
6 | "badge.take-the-challenge": "Take the Challenge",
7 | "badge.take-the-challenge-unit-test": "Take the Challenge(Passed unit tests)",
8 | "difficulty.easy": "easy",
9 | "difficulty.extreme": "extreme",
10 | "difficulty.hard": "hard",
11 | "difficulty.medium": "medium",
12 | "difficulty.warm": "warm-up",
13 | "display": "English"
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/locales/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "badge.back": "返回首页",
3 | "badge.checkout-solutions": "查看解答",
4 | "badge.preview-playground": "在 Playground 中预览",
5 | "badge.share-your-solutions": "分享你的解答",
6 | "badge.take-the-challenge": "接受挑战",
7 | "badge.take-the-challenge-unit-test": "接受挑战(通过单元测试)",
8 | "difficulty.easy": "简单",
9 | "difficulty.extreme": "地狱",
10 | "difficulty.hard": "困难",
11 | "difficulty.medium": "中等",
12 | "difficulty.warm": "热身",
13 | "display": "简体中文"
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vuejs-challenges/scripts",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "@actions/core": "^1.2.7",
6 | "@actions/github": "^4.0.0",
7 | "@actions/io": "^1.1.0",
8 | "@type-challenges/octokit-create-pull-request": "^0.1.8",
9 | "fast-glob": "^3.2.5",
10 | "fflate": "^0.7.3",
11 | "fs-extra": "^9.1.0",
12 | "google-translate-open-api": "^1.3.7",
13 | "js-yaml": "^4.1.0",
14 | "limax": "^2.1.0",
15 | "tsx": "^3.6.0"
16 | },
17 | "private": true
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/stackblitz/index.ts:
--------------------------------------------------------------------------------
1 | import { STACKBLITZ_PLAYGROUND_URL } from "../configs"
2 | import template from "./template"
3 | export interface StackBlitzPayloadOptions {
4 | title: string
5 | files: Record
6 | openFile: string
7 | }
8 |
9 | export function normalizePayload(payload: StackBlitzPayloadOptions) {
10 | return {
11 | openFile: payload.openFile,
12 | files: {
13 | "package.json": template.packageJSONContent,
14 | "package-lock.json": template.lock,
15 | "vite.config.ts": template.viteConfigContenet,
16 | "main.ts": template.mainTsContent,
17 | "index.html": template.indexHtmlContent,
18 | "env.d.ts": template.envTsContent,
19 | ...payload.files,
20 | },
21 | title: payload.title,
22 | description: payload.title,
23 | template: "node",
24 | }
25 | }
26 |
27 | function serialize(data: string): string {
28 | return btoa(unescape(encodeURIComponent(data)))
29 | }
30 |
31 | export function normalizeStackBlitzLink(payload: StackBlitzPayloadOptions) {
32 | return `${STACKBLITZ_PLAYGROUND_URL}#${serialize(JSON.stringify(normalizePayload(payload)))}`
33 | }
34 |
--------------------------------------------------------------------------------
/scripts/stackblitz/template.ts:
--------------------------------------------------------------------------------
1 |
2 | import { lock } from "./package-lock"
3 |
4 | const packageJSONContent = JSON.stringify({
5 | scripts: {
6 | "test": "vitest --environment jsdom",
7 | "test:ui": "vitest --environment jsdom --ui",
8 | "dev": "vite",
9 | "init": "concurrently \"npm run dev\" \"npm run test\" ",
10 | },
11 | stackblitz: {
12 | startCommand: "npm run init",
13 | },
14 | dependencies: {
15 | vue: "^3.2.37",
16 | },
17 | devDependencies: {
18 | "@types/jsdom": "^16.2.14",
19 | "@vitejs/plugin-vue": "^2.3.3",
20 | "@vitest/ui": "^0.17.0",
21 | "@vue/test-utils": "^2.0.2",
22 | "jsdom": "^20.0.0",
23 | "vite": "^2.9.13",
24 | "typescript": "^4.7.4",
25 | "vite-plugin-vue-inspector": "1.0.1",
26 | "vitest": "^0.17.0",
27 | "concurrently": "^7.2.2",
28 | },
29 | })
30 |
31 | const viteConfigContenet = `
32 | import { defineConfig } from 'vite';
33 | import vue from '@vitejs/plugin-vue';
34 | import Inspector from 'vite-plugin-vue-inspector';
35 |
36 | export default defineConfig({
37 | plugins: [
38 | vue(),
39 | Inspector({
40 | enabled: false,
41 | toggleButtonVisibility: 'never'
42 | }),
43 | ],
44 | });
45 | `
46 |
47 | const mainTsContent = `
48 | import { createApp } from 'vue'
49 | import App from './App.vue'
50 |
51 | createApp(App).mount('#app')
52 | `
53 |
54 | const indexHtmlContent = `
55 |
56 |
57 |
58 |
59 |
60 |
61 | Vite App
62 |
63 |
64 |
65 |
66 |
67 |
68 | `
69 |
70 | const envTsContent = `
71 | ///
72 |
73 | declare module '*.vue' {
74 | import type { DefineComponent } from 'vue';
75 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
76 | const component: DefineComponent<{}, {}, any>;
77 | export default component;
78 | }
79 | `
80 | export default {
81 | packageJSONContent,
82 | viteConfigContenet,
83 | mainTsContent,
84 | indexHtmlContent,
85 | envTsContent,
86 | lock,
87 | }
88 |
--------------------------------------------------------------------------------
/scripts/types.ts:
--------------------------------------------------------------------------------
1 | import type { getOctokit, context } from "@actions/github"
2 | import type Core from "@actions/core"
3 | import type IO from "@actions/io"
4 |
5 | export interface QuizMetaInfo {
6 | title: string
7 | author: {
8 | name: string
9 | github: string
10 | }
11 | difficulty: string
12 | tags: []
13 | }
14 |
15 | export interface Quiz {
16 | no: number
17 | quizLink: string
18 | stackblitzLink: Record
19 | path: string
20 | readme: Record
21 | info: Partial | undefined
22 | }
23 |
24 | export type Github = ReturnType
25 | export type Context = typeof context
26 | export type Action = (github: Github, context: Context, core: typeof Core, io: typeof IO) => Promise
27 |
--------------------------------------------------------------------------------
/scripts/utils.ts:
--------------------------------------------------------------------------------
1 | import { zlibSync, strToU8, strFromU8 } from "fflate"
2 | import { defaultLocale } from "./locales"
3 |
4 | export function utoa(data: string): string {
5 | const buffer = strToU8(data)
6 | const zipped = zlibSync(buffer, { level: 9 })
7 | const binary = strFromU8(zipped, true)
8 | return btoa(binary)
9 | }
10 |
11 | export function serialize(files) {
12 | return `#${utoa(JSON.stringify(files))}`
13 | }
14 |
15 | export function escapeHtml(unsafe: string) {
16 | return unsafe
17 | .replace(/&/g, "&")
18 | .replace(//g, ">")
20 | .replace(/"/g, """)
21 | .replace(/'/g, "'")
22 | }
23 |
24 | export function resolveFilePath(dir: string, name: string, ext: string, locale: string) {
25 | if (locale === defaultLocale)
26 | return `${dir}/${name}.${ext}`
27 | else
28 | return `${dir}/${name}.${locale}.${ext}`
29 | }
30 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config"
2 | import vue from "@vitejs/plugin-vue"
3 |
4 | export default defineConfig({
5 | plugins: [vue()],
6 | })
7 |
--------------------------------------------------------------------------------