vue#reactive TypeScript Examples

The following examples show how to use vue#reactive. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Store.ts    From universal-model-vue with MIT License 6 votes vote down vote up
constructor(initialState: T, selectors?: Selectors<T, U>) {
    this.reactiveState = reactive(initialState);
    this.reactiveSelectors = {} as ComputedSelectors<T, U>;
    if (selectors) {
      Object.keys(selectors).forEach(
        (key: keyof U) =>
          (this.reactiveSelectors[key] = computed(() =>
            // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
            // @ts-ignore
            selectors[key](this.reactiveState)
          ))
      );
    }
  }
Example #2
Source File: scope-element.ts    From quantum-sheet with GNU General Public License v3.0 6 votes vote down vote up
/*const imports = computed(() => {
    variables.forEach((value, key) => {
      if(value[0].getters.length > 0) {
        .push()
      }
    });
  })*/

  private createVariableArray(name: string): ScopedVariable[] {
    // First variable, used to import values from the scope above
    const importerVariable: ScopedVariable = reactive({
      position: computed(() => this.position.value),
      index: 0,
      data: shallowRef(),
      getters: [],
    })

    const newVariableArray = reactive([importerVariable])
    // Cleanup if unused
    watch([() => newVariableArray.length, () => importerVariable.getters.length], ([variableArrayLength, gettersLength]) => {
      if (variableArrayLength <= 1 && gettersLength == 0) {
        this._variableMap.delete(name)
      }
    })

    this._variableMap.set(name, newVariableArray)
    return newVariableArray
  }
Example #3
Source File: useParent.ts    From elenext with MIT License 6 votes vote down vote up
useChildren = <T extends Record<string, unknown>>(key: InjectionKey<ParentProvide<T>>, data: T) => {
  const children: ComponentInternalInstance[] = reactive([])
  provide(key, {
    ...data,
    children,
    insert: (child: ComponentInternalInstance) => {
      children.push(child)
    },
    remove: (child: ComponentInternalInstance) => {
      const index = children.indexOf(child)
      children.splice(index, 1)
    }
  })
}
Example #4
Source File: config.ts    From elenext with MIT License 6 votes vote down vote up
EConfigProvider = defineComponent({
  name: '',
  props: { theme: vptypes.object() },
  setup(props, { slots }) {
    const config = reactive({
      theme: props.theme,
    })
    provide(EConfigProvider_IJK, config)
    return slots.defalut?.()
  },
})
Example #5
Source File: useHistory.ts    From vhook with MIT License 6 votes vote down vote up
export function useHistory(): IHistoryResult {
  const state: UnwrapRef<IHistoryState> = reactive(buildState())
  const [, clearPopStateListener] = useEvent('popstate', buildState)
  const [, clearPushStateListener] = useEvent('pushstate' as any, buildState)
  const [, clearReplaceStateListener] = useEvent('replacestate' as any, buildState)
  const clear = () => {
    clearPopStateListener()
    clearPushStateListener()
    clearReplaceStateListener()
  }
  return {
    ...toRefs(state),
    clear
  }
}
Example #6
Source File: use-compitable.spec.ts    From vooks with MIT License 6 votes vote down vote up
describe('# useCompitable', () => {
  it('works', () => {
    const props: {
      oldKey?: number
      newKey?: number
    } = reactive({
      oldKey: undefined,
      newKey: 1
    })
    const compitableRef = useCompitable(props, ['oldKey', 'newKey'])
    expect(compitableRef.value).toEqual(1)
    props.oldKey = 2
    expect(compitableRef.value).toEqual(2)
    props.oldKey = undefined
    props.newKey = undefined
    expect(compitableRef.value).toEqual(undefined)
  })
})
Example #7
Source File: state.ts    From vue-keycloak with Apache License 2.0 6 votes vote down vote up
state = reactive<KeycloakState>({
  isAuthenticated: false,
  hasFailed: false,
  isPending: false,
  token: '',
  decodedToken: {},
  username: '',
  roles: [] as string[],
  resourceRoles: {},
})
Example #8
Source File: app.ts    From vite-plugin-ssr with MIT License 5 votes vote down vote up
function createApp(pageContext: PageContext) {
  const { Page } = pageContext

  let rootComponent: Component
  const PageWithWrapper = defineComponent({
    data: () => ({
      Page: markRaw(Page),
      pageProps: markRaw(pageContext.pageProps || {}),
    }),
    created() {
      rootComponent = this
    },
    render() {
      return h(
        PageShell,
        {},
        {
          default: () => {
            return h(this.Page, this.pageProps)
          },
        },
      )
    },
  })

  const app = createSSRApp(PageWithWrapper)

  // We use `app.changePage()` to do Client Routing, see `_default.page.client.js`
  objectAssign(app, {
    changePage: (pageContext: PageContext) => {
      Object.assign(pageContextReactive, pageContext)
      rootComponent.Page = markRaw(pageContext.Page)
      rootComponent.pageProps = markRaw(pageContext.pageProps || {})
    },
  })

  // When doing Client Routing, we mutate pageContext (see usage of `app.changePage()` in `_default.page.client.js`).
  // We therefore use a reactive pageContext.
  const pageContextReactive = reactive(pageContext)

  // Make `pageContext` accessible from any Vue component
  setPageContext(app, pageContextReactive)

  return app
}
Example #9
Source File: store.ts    From ncov with MIT License 5 votes vote down vote up
globalState = reactive({
    cancelNextNavigation: false,
})
Example #10
Source File: scope-element.ts    From quantum-sheet with GNU General Public License v3.0 5 votes vote down vote up
private readonly _variableMap = reactive(new Map<string, ScopedVariable[]>())
Example #11
Source File: scope-element.ts    From quantum-sheet with GNU General Public License v3.0 5 votes vote down vote up
addGetter(name: string, position: ComputedRef<Vector2>): UseScopedGetter {
    const getter: ScopedGetter = reactive({
      position: position,
      variable: undefined,
    })
    const data = computed(() => getter.variable?.data)

    const variableArray = this.variableMap.get(name) ?? this.createVariableArray(name)

    watchImmediate(
      () => getter.position,
      (value) => {
        if (getter.variable) {
          // If the getter is still in the correct position, bail out
          const nextVariable = arrayUtils.at(variableArray, getter.variable.index + 1)
          if (
            isInRange(value, {
              start: getter.variable.position,
              end: nextVariable?.position,
            })
          ) {
            return
          }

          // Remove getter from old variable
          arrayUtils.remove(getter.variable.getters, getter)
          getter.variable = undefined
        }

        const { index } = arrayUtils.getBinaryInsertIndex(variableArray, (v) => v.position.compareTo(value))

        const variable = arrayUtils.at(variableArray, index - 1)
        assert(variable, `Getter position ${getter.position} outside of block ${this.position}`)

        // Add getter to variable
        variable.getters.push(getter)
        getter.variable = variable
      }
    )

    function remove() {
      if (!getter.variable) return
      arrayUtils.remove(getter.variable.getters, getter)
      getter.variable = undefined
    }

    return {
      data,
      remove,
    }
  }
Example #12
Source File: scope-element.ts    From quantum-sheet with GNU General Public License v3.0 5 votes vote down vote up
addVariable(name: string, position: ComputedRef<Vector2>): UseScopedVariable {
    // Add variable
    const variable: ScopedVariable = reactive({
      position: position,
      index: -1,
      data: shallowRef<any>(null),
      getters: [],
    })

    const variableArray = this.variableMap.get(name) ?? this.createVariableArray(name)

    watchImmediate(
      () => variable.position,
      (value) => {
        // Remove (or bail out)
        if (variable.index >= 0) {
          assert(variableArray[variable.index] == variable, `Expected variable ${variable} to be in ${variableArray} at index ${variable.index}`)

          const prev = arrayUtils.at(variableArray, variable.index - 1)
          const next = arrayUtils.at(variableArray, variable.index + 1)

          if (isInRange(value, { start: prev?.position, end: next?.position })) {
            // TODO: Optimize?
            // Currently this doesn't account for moving a variable past its getters
            // return
          }

          removeVariable(variableArray, variable)
        }

        // Add
        const { index } = arrayUtils.getBinaryInsertIndex(variableArray, (v) => v.position.compareTo(value))

        const prev = arrayUtils.at(variableArray, index - 1)
        // Take some getters from prev
        if (prev?.getters) {
          // Warning: This has to be strictly less than 0. If they are on the same spot, the getter belongs to the previous variable
          variable.getters = prev.getters.filter((v) => value.compareTo(v.position) < 0)
          variable.getters.forEach((v) => {
            v.variable = variable
          })
          prev.getters = prev.getters.filter((v) => !(value.compareTo(v.position) < 0))
        }
        // Update variable indices
        for (let i = index; i < variableArray.length; i++) {
          variableArray[i].index = i + 1
        }
        variableArray.splice(index, 0, variable)
        variable.index = index
      }
    )

    function setData(data: Expression | null | undefined) {
      variable.data = data
    }

    function remove() {
      removeVariable(variableArray, variable)
    }

    return {
      setData,
      remove,
    }
  }
Example #13
Source File: index.ts    From vue3-gettext with MIT License 5 votes vote down vote up
export function createGettext(options: Partial<GetTextOptions> = {}) {
  Object.keys(options).forEach((key) => {
    if (Object.keys(defaultOptions).indexOf(key) === -1) {
      throw new Error(`${key} is an invalid option for the translate plugin.`);
    }
  });

  const mergedOptions = {
    ...defaultOptions,
    ...options,
  };

  const translations = ref(normalizeTranslations(mergedOptions.translations));

  const gettext: Language = reactive({
    available: mergedOptions.availableLanguages,
    muted: mergedOptions.mutedLanguages,
    silent: mergedOptions.silent,
    translations: computed({
      get: () => {
        return translations.value;
      },
      set: (val: GetTextOptions["translations"]) => {
        translations.value = normalizeTranslations(val);
      },
    }),
    current: mergedOptions.defaultLanguage,
    install(app: App) {
      // TODO: is this needed?
      (app as any)[GetTextSymbol] = gettext;
      app.provide(GetTextSymbol, gettext);

      if (mergedOptions.setGlobalProperties) {
        const globalProperties = app.config.globalProperties;
        globalProperties.$gettext = gettext.$gettext;
        globalProperties.$pgettext = gettext.$pgettext;
        globalProperties.$ngettext = gettext.$ngettext;
        globalProperties.$npgettext = gettext.$npgettext;
        globalProperties.$gettextInterpolate = gettext.interpolate;
        globalProperties.$language = gettext;
      }

      if (mergedOptions.provideDirective) {
        app.directive("translate", Directive(gettext));
      }
      if (mergedOptions.provideComponent) {
        // eslint-disable-next-line vue/multi-word-component-names, vue/component-definition-name-casing
        app.component("translate", Component);
      }
    },
  }) as unknown as Language;

  const translate = translateRaw(gettext);
  const interpolate = interpolateRaw(gettext);
  gettext.$gettext = translate.gettext.bind(translate);
  gettext.$pgettext = translate.pgettext.bind(translate);
  gettext.$ngettext = translate.ngettext.bind(translate);
  gettext.$npgettext = translate.npgettext.bind(translate);
  gettext.interpolate = interpolate.bind(interpolate);

  gettext.directive = Directive(gettext);
  gettext.component = Component;

  return gettext;
}
Example #14
Source File: index.ts    From jz-gantt with MIT License 5 votes vote down vote up
initStore = () => {
  const GtData = reactive(new GanttData()) as GanttData;
  provide(Variables.provider.gtData, GtData);

  const GtParam = reactive(new ParamData()) as ParamData;
  provide(Variables.provider.gtParam, GtParam);

  const rootEmit = ref();
  provide(Variables.provider.gtRootEmit, rootEmit);

  const rootRef = ref<HTMLDivElement>();
  provide(Variables.provider.gtRootRef, rootRef);

  const ganttRef = ref<HTMLDivElement>();
  provide(Variables.provider.gtGanttRef, ganttRef);

  const tableRef = ref<HTMLDivElement>();
  provide(Variables.provider.gtTableRef, tableRef);

  const isShowMask = ref(false);
  provide(Variables.provider.gtIsShowMask, isShowMask);

  const showDateList = reactive<Date[]>([]);
  provide(Variables.provider.gtShowDateList, showDateList);

  const successBarList: Row[] = reactive([]);
  provide(Variables.provider.gtSuccessBarList, successBarList);

  const initGanttWidth = ref(0);
  provide(Variables.provider.gtInitGanttWidth, initGanttWidth);

  // 设置移动线
  const columnSliderLineVisible = ref(false);
  provide(
    Variables.provider.gtColumnSliderLineVisible,
    columnSliderLineVisible
  );
  const columnSliderLineLeft = ref(0);
  provide(Variables.provider.gtColumnSliderLineLeft, columnSliderLineLeft);
  const columnDefaultLeft = ref(-1);
  provide(Variables.provider.gtColumnDefaultLeft, columnDefaultLeft);

  const timeout = 500; // 提示条消失时间
  provide(Variables.provider.gtSuccessBarTimeout, timeout);

  // toast
  const isShowToast = ref(false);
  provide(Variables.provider.gtIsShowToast, isShowToast);
  const toastMessage = ref('');
  provide(Variables.provider.gtToastMessage, toastMessage);
  const toastQueue: any[] = [];
  provide(Variables.provider.gtToastQueue, toastQueue);

  // wheel
  const scrollTop = ref(0);
  provide(Variables.provider.gtScrollTop, scrollTop);
  const rootHeight = ref(0);
  provide(Variables.provider.gtRootHeight, rootHeight);
  const scrollBarHeight = ref(Variables.size.defaultScrollBarHeight);
  provide(Variables.provider.gtScrollBarHeight, scrollBarHeight);

  const stopClickEvent = ref(false);
  provide(Variables.provider.gtStopClickEvent, stopClickEvent);
}
Example #15
Source File: FormKitSchema.ts    From formkit with MIT License 5 votes vote down vote up
FormKitSchema = defineComponent({
  name: 'FormKitSchema',
  props: {
    schema: {
      type: [Array, Object] as PropType<
        FormKitSchemaNode[] | FormKitSchemaCondition
      >,
      required: true,
    },
    data: {
      type: Object as PropType<Record<string, any>>,
      default: () => ({}),
    },
    library: {
      type: Object as PropType<FormKitComponentLibrary>,
      default: () => ({}),
    },
  },
  setup(props, context) {
    const instance = getCurrentInstance()
    let instanceKey = Symbol(String(i++))
    instanceScopes.set(instanceKey, [])
    let provider = parseSchema(props.library, props.schema)
    let render: RenderChildren
    let data: Record<string, any>
    // Re-parse the schema if it changes:
    watch(
      () => props.schema,
      (newSchema, oldSchema) => {
        instanceKey = Symbol(String(i++))
        provider = parseSchema(props.library, props.schema)
        render = createRenderFn(provider, data, instanceKey)
        if (newSchema === oldSchema) {
          // In this edge case, someone pushed/modified something in the schema
          // and we've successfully re-parsed, but since the schema is not
          // referenced in the render function it technically isnt a dependency
          // and we need to force a re-render since we swapped out the render
          // function completely.
          ;(instance?.proxy?.$forceUpdate as unknown as CallableFunction)()
        }
      },
      { deep: true }
    )

    // Watch the data object explicitly
    watchEffect(() => {
      data = Object.assign(reactive(props.data), {
        slots: context.slots,
      })
      render = createRenderFn(provider, data, instanceKey)
    })
    return () => render()
  },
})
Example #16
Source File: provider.ts    From fect with MIT License 5 votes vote down vote up
createProvider = <
  // eslint-disable-next-line
  T extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
  ProvideValue = never
>(
  key: InjectionKey<ProvideValue>
) => {
  const publicChildren: T[] = reactive([])
  const internalChildren: ComponentInternalInstance[] = reactive([])
  const parent = getCurrentInstance()!
  const provider = (value?: ProvideValue) => {
    const link = (child: ComponentInternalInstance) => {
      if (child.proxy) {
        internalChildren.push(child)
        publicChildren.push(child.proxy as T)
        sortChildren(parent, publicChildren, internalChildren)
      }
    }

    const unlink = (child: ComponentInternalInstance) => {
      const idx = internalChildren.indexOf(child)
      publicChildren.splice(idx, 1)
      internalChildren.splice(idx, 1)
    }
    provide(
      key,
      Object.assign(
        {
          link,
          unlink,
          children: publicChildren,
          internalChildren
        },
        value
      )
    )
  }
  return {
    children: publicChildren,
    provider
  }
}
Example #17
Source File: useTree.spec.ts    From vue3-treeview with MIT License 5 votes vote down vote up
describe("test useTree", () => {
    let useTest = null;

    let props = null;

    const fakeEmit = (evt: string, ...args: any[]) => {};

    const v = require("vue");

    v.onUnmounted = jest.fn();

    const spy = jest.spyOn(v, "provide").mockImplementation(() => () => {});

    beforeEach(() => {
        props = reactive({
            config: {
                root: ["id1"]
            },
            nodes: {
                id1: {
                    text: "test"
                }
            }
        });

        useTest = useTree(props, fakeEmit);
    });

    it("Expect to call provide", () => {
        expect(spy).toBeCalledWith("emitter", fakeEmit);
    });

    it("Expect to create element ref", () => {
        expect(isRef(useTest.element)).toBeTruthy();
        expect(useTest.element.value).toBeNull();
    });

    it("Expect to have basic style", () => {
        expect(useTest.style.value).toMatchObject({
            "align-items": "center",
            "display": "flex"
        });
    });
});
Example #18
Source File: useSheetUpdate.ts    From S2 with MIT License 5 votes vote down vote up
useSheetUpdate = (
  s2Ref: ShallowRef<SpreadSheet | undefined>,
  props: BaseSheetProps,
) => {
  const updateFlag = reactive({
    rerender: false,
    reloadData: false,
    rebuildDataset: false,
  });

  watch(
    () => props.options,
    (options, prevOptions) => {
      updateFlag.rerender = true;

      if (!Object.is(prevOptions?.hierarchyType, options?.hierarchyType)) {
        // 自定义树目录需要重新构建 CustomTreePivotDataSet
        updateFlag.reloadData = true;
        updateFlag.rebuildDataset = true;
      }
      s2Ref.value?.setOptions(options as S2Options);
      s2Ref.value?.changeSheetSize(options?.width, options?.height);
    },
    { deep: isProxy(props.options) },
  );

  watch(
    () => props.dataCfg!,
    (dataCfg) => {
      updateFlag.rerender = true;
      updateFlag.reloadData = true;
      s2Ref.value?.setDataCfg(dataCfg);
    },
    { deep: isProxy(props.dataCfg) },
  );

  watch(
    () => props.themeCfg!,
    (themeCfg) => {
      updateFlag.rerender = true;
      s2Ref.value?.setThemeCfg(themeCfg);
    },
    {
      deep: isProxy(props.themeCfg),
    },
  );

  watch(updateFlag, (flag) => {
    if (!flag.rerender) {
      return;
    }
    s2Ref.value?.render(flag.reloadData, {
      reBuildDataSet: flag.rebuildDataset,
    });
    flag.rerender = false;
    flag.reloadData = false;
    flag.rebuildDataset = false;
  });
}
Example #19
Source File: useBreakpoint.ts    From elenext with MIT License 5 votes vote down vote up
screens = reactive<ScreenMap>({
  // lg: true
})
Example #20
Source File: useIcon.spec.ts    From vue3-treeview with MIT License 5 votes vote down vote up
describe("test use icon", () => {
    const config = {
        openedIcon: null,
        closedIcon: null
    };

    const nodes = {};

    const storeProps = reactive({
        nodes,
        config
    });

    let props = null;

    let useFake = null;

    let state = null;

    let v = require("vue");

    v.inject = jest.fn(() => state);

    beforeEach(() => {
        props = reactive({
            isLeaf: ref(false)
        });
        state = states.get(createState(storeProps as any));
        useFake = useIcon(props);
    });

    it("Expect to have default openIcon", () => {
        expect(useFake.openedIcon.value).toMatchObject(defaultConfig.openedIcon);
    }); 

    it("Expect to have default closeIcon", () => {
        expect(useFake.closedIcon.value).toMatchObject(defaultConfig.closedIcon);
    });

    it("Expec to have icons", () => {
        expect(useFake.hasIcons.value).toBeTruthy();
    });

    it("Expect to use icons", () => {
        expect(useFake.useIcons.value).toBeTruthy();
    });

    it("Expect to have fake no style", () => {
        expect(useFake.fakeNodeStyle.value).toMatchObject({
            height: "8px",
            width: "8px"
        });
    });

    it("Expect to set opened icons", () => {
        config.openedIcon = { 
            test: "test"
        };
        expect(useFake.openedIcon.value).toMatchObject({
            test: "test"
        });
    });

    it("Expect to set closed icons", () => {
        config.closedIcon = { 
            test: "test"
        };
        expect(useFake.closedIcon.value).toMatchObject({
            test: "test"
        });
    });

    it("Expect not to use icons", () => {
        props.isLeaf = true;
        expect(useFake.useIcons.value).toBeFalsy();
    });
});
Example #21
Source File: use-keyboard.ts    From vooks with MIT License 4 votes vote down vote up
export default function useKeyboard (
  options: useKeyboardOptions = {},
  enabledRef?: Ref<boolean>
): Readonly<UseKeyboardState> {
  const state = reactive<UseKeyboardState>({
    ctrl: false,
    command: false,
    win: false,
    shift: false,
    tab: false
  })
  const {
    keydown,
    keyup
  } = options
  const keydownHandler = (e: KeyboardEvent): void => {
    switch (e.key) {
      case 'Control':
        state.ctrl = true
        break
      case 'Meta':
        state.command = true
        state.win = true
        break
      case 'Shift':
        state.shift = true
        break
      case 'Tab':
        state.tab = true
        break
    }
    if (keydown !== undefined) {
      Object.keys(keydown).forEach(key => {
        if (key !== e.key) return
        const handler = keydown[key]
        if (typeof handler === 'function') {
          handler(e)
        } else {
          const { stop = false, prevent = false } = handler
          if (stop) e.stopPropagation()
          if (prevent) e.preventDefault()
          handler.handler(e)
        }
      })
    }
  }
  const keyupHandler = (e: KeyboardEvent): void => {
    switch (e.key) {
      case 'Control':
        state.ctrl = false
        break
      case 'Meta':
        state.command = false
        state.win = false
        break
      case 'Shift':
        state.shift = false
        break
      case 'Tab':
        state.tab = false
        break
    }
    if (keyup !== undefined) {
      Object.keys(keyup).forEach(key => {
        if (key !== e.key) return
        const handler = keyup[key]
        if (typeof handler === 'function') {
          handler(e)
        } else {
          const { stop = false, prevent = false } = handler
          if (stop) e.stopPropagation()
          if (prevent) e.preventDefault()
          handler.handler(e)
        }
      })
    }
  }
  const setup = (): void => {
    if (enabledRef === undefined || enabledRef.value) {
      on('keydown', document, keydownHandler)
      on('keyup', document, keyupHandler)
    }
    if (enabledRef !== undefined) {
      watch(enabledRef, value => {
        if (value) {
          on('keydown', document, keydownHandler)
          on('keyup', document, keyupHandler)
        } else {
          off('keydown', document, keydownHandler)
          off('keyup', document, keyupHandler)
        }
      })
    }
  }
  if (hasInstance()) {
    onBeforeMount(setup)
    onBeforeUnmount(() => {
      if (enabledRef === undefined || enabledRef.value) {
        off('keydown', document, keydownHandler)
        off('keyup', document, keyupHandler)
      }
    })
  } else {
    setup()
  }
  return readonly(state)
}
Example #22
Source File: setting.ts    From novel-downloader with GNU Affero General Public License v3.0 4 votes vote down vote up
vm = createApp({
  name: "nd-setting",
  components: { "filter-tab": FilterTab, "log-ui": LogUI, "test-ui": TestUI },
  setup() {
    interface Setting {
      enableDebug?: boolean;
      enableTestPage?: boolean;
      chooseSaveOption?: string;
      filterSetting?: filterSettingGlobal;
      currentTab: string;
    }

    const setting = reactive({} as Setting);
    let settingBackup = {};

    interface SaveOption {
      key: string;
      value: string;
      options: globalSaveOptions;
    }

    const saveOptions: SaveOption[] = [
      { key: "null", value: "不使用自定义保存参数", options: {} },
      {
        key: "chapter_name",
        value: "将章节名称格式修改为 第xx章 xxxx",
        options: {
          getchapterName: (chapter) => {
            if (chapter.chapterName) {
              return `第${chapter.chapterNumber.toString()}${
                chapter.chapterName
              }`;
            } else {
              return `第${chapter.chapterNumber.toString()}章`;
            }
          },
        },
      },
      {
        key: "txt_space",
        value: "txt文档每个自然段前加两个空格",
        options: {
          genChapterText: (chapterName, contentText) => {
            contentText = contentText
              .split("\n")
              .map((line) => {
                if (line.trim() === "") {
                  return line;
                } else {
                  return line.replace(/^/, "    ");
                }
              })
              .join("\n");
            return `## ${chapterName}\n\n${contentText}\n\n`;
          },
        },
      },
      {
        key: "reverse_chapters",
        value: "保存章节时倒序排列",
        options: {
          chapterSort: (a, b) => {
            if (a.chapterNumber > b.chapterNumber) {
              return -1;
            }
            if (a.chapterNumber === b.chapterNumber) {
              return 0;
            }
            if (a.chapterNumber < b.chapterNumber) {
              return 1;
            }
            return 0;
          },
        },
      },
    ];
    setting.enableDebug = enableDebug.value;
    setting.chooseSaveOption = "null";
    setting.enableTestPage = false;
    setting.currentTab = "tab-1";
    const curSaveOption = () => {
      const _s = saveOptions.find((s) => s.key === setting.chooseSaveOption);
      if (_s) {
        return _s.options;
      } else {
        return saveOptions[0].options;
      }
    };
    const saveFilter = (filterSetting: filterSettingGlobal) => {
      setting.filterSetting = deepcopy(filterSetting);
    };
    const getFilterSetting = () => {
      if (setting.filterSetting) {
        return setting.filterSetting;
      } else {
        return;
      }
    };
    provide("getFilterSetting", getFilterSetting);

    const setConfig = (config: Setting) => {
      setEnableDebug();
      setCustomSaveOption();
      setCustomFilter();

      function setEnableDebug() {
        if (typeof config.enableDebug === "boolean") {
          config.enableDebug ? log.setLevel("trace") : log.setLevel("info");
          enableDebug.value = config.enableDebug;
          if (config.enableDebug) {
            debug();
          }
          log.info(`[Init]enableDebug: ${enableDebug.value}`);
        }
      }

      function setCustomSaveOption() {
        (unsafeWindow as UnsafeWindow).saveOptions = curSaveOption();
      }

      function setCustomFilter() {
        if (config.filterSetting) {
          if (config.filterSetting.filterType === "null") {
            (unsafeWindow as UnsafeWindow).chapterFilter = undefined;
          } else {
            const filterFunction = getFilterFunction(
              config.filterSetting.arg,
              config.filterSetting.functionBody
            );
            if (filterFunction) {
              (unsafeWindow as UnsafeWindow).chapterFilter = (
                chapter: Chapter
              ) => {
                if (chapter.status === Status.aborted) {
                  return false;
                }
                return filterFunction(chapter);
              };
            }
          }
        }
      }
    };

    const openStatus = ref("false");
    const openSetting = () => {
      settingBackup = deepcopy(setting) as Setting;
      openStatus.value = "true";
    };
    const closeSetting = (keep: PointerEvent | boolean) => {
      if (keep === true) {
        settingBackup = deepcopy(setting);
      } else {
        Object.assign(setting, settingBackup);
      }
      openStatus.value = "false";
    };
    const closeAndSaveSetting = async () => {
      closeSetting(true);
      await sleep(30);
      setConfig(deepcopy(setting));
      log.info("[Init]自定义设置:" + JSON.stringify(setting));
    };

    return {
      openStatus,
      openSetting,
      closeSetting,
      closeAndSaveSetting,
      saveFilter,
      setting,
      saveOptions,
    };
  },
  template: settingHtml,
}).mount(el)
Example #23
Source File: index.test.tsx    From vue-request with MIT License 4 votes vote down vote up
describe('useRequest', () => {
  beforeAll(() => {
    jest.useFakeTimers('modern');
  });

  const successApi = 'http://example.com/200';
  const failApi = 'http://example.com/404';
  // mock fetch
  fetchMock.get(successApi, { data: 'success' });
  fetchMock.get(failApi, 404);

  const originalError = console.error;
  beforeEach(() => {
    console.error = jest.fn();
    // clear cache
    clearCache();
    // clear global options
    clearGlobalOptions();

    // clear listener
    RECONNECT_LISTENER.clear();
    FOCUS_LISTENER.clear();
    VISIBLE_LISTENER.clear();
  });

  afterEach(() => {
    console.error = originalError;
  });

  test('should be defined', () => {
    expect(useRequest).toBeDefined();
  });

  test('should auto run', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(request);

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
  });

  test('can be manually triggered', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run } = useRequest(request, { manual: true });

          return () => (
            <button onClick={() => run()}>{`data:${data.value}`}</button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:undefined');
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
  });

  test('params should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, params } = useRequest(request, {
            defaultParams: ['hello', 'world'],
          });
          return () => (
            <button onClick={() => run('hi there')}>
              {params.value?.join(',')}
            </button>
          );
        },
      }),
    );

    await waitForTime(1000);
    expect(wrapper.text()).toBe('hello,world');
    await wrapper.find('button').trigger('click');
    await waitForTime(1000);
    expect(wrapper.text()).toEqual('hi there');
  });

  test('defaultParams should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(request, {
            defaultParams: ['hello', 'world'],
          });

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:hello,world');
  });

  test('run can be accept params', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run } = useRequest(request);

          return () => (
            <button onClick={() => run('hello', 'world')}>
              {`data:${data.value}`}
            </button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(wrapper.text()).toBe('data:hello,world');
  });

  test('mutate should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, mutate } = useRequest(request);

          return () => (
            <button onClick={() => mutate('ok')}>{`data:${data.value}`}</button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('data:ok');
  });

  test('mutate callback should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, mutate } = useRequest(request);

          return () => (
            <button onClick={() => mutate(() => 'ok')}>
              {`data:${data.value}`}
            </button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('data:ok');
  });

  test('refresh should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { refresh, loading } = useRequest(request);

          return () => (
            <button onClick={() => refresh()}>
              {`loading:${loading.value}`}
            </button>
          );
        },
      }),
    );
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('loading:true');
    await waitForAll();
    expect(wrapper.text()).toBe('loading:false');
  });

  test('log request error by default', async () => {
    console.error = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(failedRequest, { manual: true });
          const handleClick = () => run();
          return () => <button onClick={handleClick}></button>;
        },
      }),
    );
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(console.error).toHaveBeenCalledWith(new Error('fail'));
  });

  test('onSuccess should work', async () => {
    const mockSuccessCallback = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(request, {
            manual: true,
            onSuccess: mockSuccessCallback,
          });
          const handleClick = () => run();
          return () => <button onClick={handleClick}></button>;
        },
      }),
    );
    await wrapper.find('button').trigger('click');

    await waitForAll();
    expect(mockSuccessCallback).toHaveBeenCalledWith('success', []);
  });

  test('onError should work', async () => {
    const mockErrorCallback = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(failedRequest, {
            manual: true,
            onError: mockErrorCallback,
          });
          const handleClick = () => run();
          return () => <button onClick={handleClick}></button>;
        },
      }),
    );
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(mockErrorCallback).toHaveBeenCalledWith(new Error('fail'), []);
  });

  test('initData should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(request, {
            initialData: 'init',
          });

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );
    expect(wrapper.text()).toBe('data:init');
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
  });

  test('ready should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const readyRef = ref(false);

          const { data } = useRequest(request, {
            ready: readyRef,
          });

          return () => (
            <button
              onClick={() => {
                readyRef.value = true;
              }}
            >
              {`data:${data.value}`}
            </button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:undefined');
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(wrapper.text()).toBe('data:success');
  });

  test('ready should save the first time request params : case 1', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const readyRef = ref(false);
          const { data, run } = useRequest(request, {
            ready: readyRef,
            defaultParams: ['default'],
          });

          return () => (
            <div>
              <button id="run" onClick={() => run('run')} />
              <button
                id="ready"
                onClick={() => {
                  readyRef.value = true;
                }}
              />
              <span id="text">{`data:${data.value}`}</span>
            </div>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.find('#text').text()).toBe('data:undefined');
    await wrapper.find('#ready').trigger('click');
    await waitForAll();
    expect(wrapper.find('#text').text()).toBe('data:default');
    await wrapper.find('#run').trigger('click');
    await waitForAll();
    expect(wrapper.find('#text').text()).toBe('data:run');
  });

  test('ready should save the first time request params : case 2', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const readyRef = ref(false);
          const { data, run } = useRequest(request, {
            ready: readyRef,
            defaultParams: ['default'],
          });

          return () => (
            <div>
              <button id="run" onClick={() => run('run')} />
              <button
                id="ready"
                onClick={() => {
                  readyRef.value = true;
                }}
              />
              <span id="text">{`data:${data.value}`}</span>
            </div>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.find('#text').text()).toBe('data:undefined');
    await wrapper.find('#run').trigger('click');
    await wrapper.find('#ready').trigger('click');
    await waitForAll();
    expect(wrapper.find('#text').text()).toBe('data:run');
  });

  test('track ready when ready initial value is false', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const readyRef = ref(true);
          const count = ref(0);
          const { data, run } = useRequest(request, {
            ready: readyRef,
            defaultParams: [count.value],
          });

          return () => (
            <button
              onClick={() => {
                readyRef.value = !readyRef.value;
                count.value += 1;
                run(count.value);
              }}
            >
              {`data:${data.value}`}
            </button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:0');
    await wrapper.find('button').trigger('click');
    await waitForAll();
    expect(wrapper.text()).toBe('data:1');
  });

  test('ready should work only once', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const readyRef = ref(false);
          const count = ref(0);
          const { data, run } = useRequest(request, {
            ready: readyRef,
            defaultParams: [count.value],
          });

          return () => (
            <button
              onClick={async () => {
                readyRef.value = !readyRef.value;
                count.value += 1;
                run(count.value);
              }}
            >
              {`data:${data.value}`}
            </button>
          );
        },
      }),
    );
    await waitForAll();
    expect(wrapper.text()).toBe('data:undefined');
    await wrapper.find('button').trigger('click');
    // first click
    await waitForAll();
    expect(wrapper.text()).toBe('data:1');
    await wrapper.find('button').trigger('click');
    // second click
    await waitForAll();
    expect(wrapper.text()).toBe('data:2');
  });

  test('formatResult should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(request, {
            formatResult: () => 'formatted',
          });

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );
    expect(wrapper.text()).toBe('data:undefined');
    await waitForAll();
    expect(wrapper.text()).toBe('data:formatted');
  });

  test('refreshDeps should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const refreshRef = ref(0);
          const refreshReactive = reactive({
            count: 0,
          });
          const { loading } = useRequest(request, {
            refreshDeps: [refreshRef, () => refreshReactive.count],
          });

          return () => (
            <div>
              <div id="data">{String(loading.value)}</div>
              <button
                id="ref"
                onClick={() => {
                  refreshRef.value++;
                }}
              />
              <button
                id="reactive"
                onClick={() => {
                  refreshReactive.count++;
                }}
              />
            </div>
          );
        },
      }),
    );

    await waitForTime(1000);
    expect(wrapper.find('#data').text()).toBe('false');

    for (let index = 0; index < 100; index++) {
      await wrapper.find('#ref').trigger('click');
      expect(wrapper.find('#data').text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.find('#data').text()).toBe('false');
    }

    for (let index = 0; index < 100; index++) {
      await wrapper.find('#reactive').trigger('click');
      expect(wrapper.find('#data').text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.find('#data').text()).toBe('false');
    }
  });

  test('loadingDelay should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading } = useRequest(request, {
            loadingDelay: 800,
          });

          return () => <button>{`loading:${loading.value}`}</button>;
        },
      }),
    );

    expect(wrapper.text()).toBe('loading:false');
    await waitForTime(800);
    expect(wrapper.text()).toBe('loading:true');
    await waitForTime(200);
    expect(wrapper.text()).toBe('loading:false');
  });

  test('cancel loadingDelay should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, cancel } = useRequest(request, {
            loadingDelay: 800,
          });

          return () => (
            <button onClick={() => cancel()}>
              {`loading:${loading.value}`}
            </button>
          );
        },
      }),
    );

    expect(wrapper.text()).toBe('loading:false');
    await waitForTime(800);
    expect(wrapper.text()).toBe('loading:true');
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('loading:false');
  });

  test('cancel should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { cancel, data, run } = useRequest(request);

          return () => (
            <div>
              <button onClick={() => cancel()} id="cancel" />
              <button onClick={() => run()} id="run" />
              <span id="data">{`data:${data.value}`}</span>
            </div>
          );
        },
      }),
    );

    expect(wrapper.find('#data').text()).toBe('data:undefined');
    await wrapper.find('#cancel').trigger('click');
    await waitForAll();
    expect(wrapper.find('#data').text()).toBe('data:undefined');
    await wrapper.find('#run').trigger('click');
    await waitForAll();
    expect(wrapper.find('#data').text()).toBe('data:success');
  });

  test('cancel should work when request error', async () => {
    console.error = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run, cancel } = useRequest(failedRequest, {
            manual: true,
          });
          return () => (
            <div>
              <button id="run" onClick={() => run().catch(() => {})}></button>;
              <button id="cancel" onClick={() => cancel()}></button>;
              <span id="data">{`data:${data.value}`}</span>
            </div>
          );
        },
      }),
    );
    expect(wrapper.find('#data').text()).toBe('data:undefined');
    await wrapper.find('#run').trigger('click');
    await waitForTime(200);
    await wrapper.find('#cancel').trigger('click');
    await waitForAll();
    expect(wrapper.find('#data').text()).toBe('data:undefined');
    await wrapper.find('#run').trigger('click');
    await waitForAll();
    expect(console.error).toHaveBeenCalledWith(new Error('fail'));
  });

  test('pollingInterval should work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, cancel } = useRequest(request, {
            pollingInterval: 500,
          });

          return () => (
            <button onClick={() => cancel()}>
              {`loading:${loading.value}`}
            </button>
          );
        },
      }),
    );

    expect(wrapper.text()).toBe('loading:true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('loading:false');
    await waitForTime(500);
    expect(wrapper.text()).toBe('loading:true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('loading:false');
    await wrapper.find('button').trigger('click');
    waitForTime(600);
    expect(wrapper.text()).toBe('loading:false');
  });

  test('pollingInterval less than 0 should not work', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, cancel } = useRequest(request, {
            pollingInterval: -0.1,
          });

          return () => (
            <button onClick={() => cancel()}>
              {`loading:${loading.value}`}
            </button>
          );
        },
      }),
    );

    expect(wrapper.text()).toBe('loading:true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('loading:false');
    await waitForTime(10);
    expect(wrapper.text()).toBe('loading:false');
  });

  test('pollingWhenHidden be false should work', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(() => request((count += 1)), {
            pollingInterval: 1000,
            pollingWhenHidden: false,
          });

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );

    expect(wrapper.text()).toBe('data:undefined');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:1');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:2');
    // mock tab hide
    Object.defineProperty(document, 'visibilityState', {
      value: 'hidden',
      writable: true,
    });
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:3');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:3');
    // mock tab show
    Object.defineProperty(document, 'visibilityState', {
      value: 'visible',
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:4');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:5');
  });

  test('pollingWhenHidden be true should work', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data } = useRequest(() => request((count += 1)), {
            pollingInterval: 1000,
            pollingWhenHidden: true,
          });

          return () => <button>{`data:${data.value}`}</button>;
        },
      }),
    );

    expect(wrapper.text()).toBe('data:undefined');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:1');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:2');
    // mock tab hide
    Object.defineProperty(document, 'visibilityState', {
      value: 'hidden',
      writable: true,
    });
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:3');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:4');
    // mock tab show
    Object.defineProperty(document, 'visibilityState', {
      value: 'visible',
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    // because pollingWhenHidden is true, so refresh never trigger
    expect(wrapper.text()).toBe('data:4');
    await waitForTime(2000);
    expect(wrapper.text()).toBe('data:5');
  });

  test('refreshOnWindowFocus should work', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run } = useRequest(() => request((count += 1)), {
            refreshOnWindowFocus: true,
          });

          return () => (
            <button onClick={() => run()}>{`data:${data.value}`}</button>
          );
        },
      }),
    );

    expect(wrapper.text()).toBe('data:undefined');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:1');
    await wrapper.find('button').trigger('click');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:2');
    // mock tab visible
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:3');
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:3');
    // wait for 5s
    await waitForTime(4000);
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:4');
  });

  test('refocusTimespan should work', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run } = useRequest(() => request((count += 1)), {
            refreshOnWindowFocus: true,
            refocusTimespan: 3000,
          });

          return () => (
            <button onClick={() => run()}>{`data:${data.value}`}</button>
          );
        },
      }),
    );

    expect(wrapper.text()).toBe('data:undefined');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:1');
    await wrapper.find('button').trigger('click');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:2');
    // mock tab visible
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:3');
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:3');
    // wait for 3s
    await waitForTime(2000);
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1000);
    expect(wrapper.text()).toBe('data:4');
  });

  test('debounceInterval should work', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 100,
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );
    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(1);

    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(2);
  });

  test('debounceOptions should work: case 1', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 100,
              debounceOptions: {
                leading: true,
                trailing: false,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );
    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(1);
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(1);

    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(2);
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(2);
  });

  test('debounceOptions should work: case 2', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 100,
              debounceOptions: {
                leading: false,
                trailing: false,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );
    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(0);
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(0);

    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(0);
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(0);
  });

  test('debounceOptions should work: case 3', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 500,
              debounceOptions: {
                maxWait: 1000,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );
    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(5);
    await waitForTime(1000);
    expect(mockFn).toHaveBeenCalledTimes(5);

    for (let index = 0; index < 100; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(50);
    }

    expect(mockFn).toHaveBeenCalledTimes(10);
    await waitForTime(1000);
    expect(mockFn).toHaveBeenCalledTimes(10);
  });

  test('debounceInterval should work with cancel', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, cancel } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 100,
              manual: true,
            },
          );
          return () => (
            <div>
              <button id="run" onClick={() => run()} />
              <button id="cancel" onClick={() => cancel()} />
            </div>
          );
        },
      }),
    );
    const run = () => wrapper.find('#run').trigger('click');
    const cancel = () => wrapper.find('#cancel').trigger('click');
    for (let index = 0; index < 100; index++) {
      await run();
      await waitForTime(50);
    }
    await cancel();
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(0);

    for (let index = 0; index < 100; index++) {
      await run();
      await waitForTime(50);
    }

    await cancel();
    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(0);
  });

  test('initial auto run should skip debounce', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              debounceInterval: 100,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );
    expect(mockFn).toHaveBeenCalledTimes(1);

    await wrapper.find('button').trigger('click');
    await waitForTime(50);
    expect(mockFn).toHaveBeenCalledTimes(1);

    await waitForTime(100);
    expect(mockFn).toHaveBeenCalledTimes(2);
  });

  test('throttleInterval should work', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              throttleInterval: 100,
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );

    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForAll();
    // have been call 3 times
    // because the function will invoking on the leading edge and trailing edge of the timeout
    expect(mockFn).toHaveBeenCalledTimes(3);
  });

  test('throttleOptions should work, case: 1', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              throttleInterval: 100,
              throttleOptions: {
                leading: false,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );

    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForAll();
    // have been call 2 times
    // because the function will only invoking on the trailing edge of the timeout
    expect(mockFn).toHaveBeenCalledTimes(2);
  });

  test('throttleOptions should work, case: 2', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              throttleInterval: 100,
              throttleOptions: {
                trailing: false,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );

    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForAll();
    // have been call 2 times
    // because the function will only invoking on the leading edge of the timeout
    expect(mockFn).toHaveBeenCalledTimes(2);
  });

  test('throttleOptions should work, case: 3', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              throttleInterval: 100,
              throttleOptions: {
                leading: false,
                trailing: false,
              },
              manual: true,
            },
          );
          return () => <button onClick={() => run()} />;
        },
      }),
    );

    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForTime(50);
    await wrapper.find('button').trigger('click');

    await waitForAll();
    expect(mockFn).toHaveBeenCalledTimes(0);
  });

  test('throttleInterval should work with cancel', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, cancel } = useRequest(
            () => {
              mockFn();
              return request();
            },
            {
              throttleInterval: 100,
              manual: true,
            },
          );
          return () => (
            <div>
              <button id="run" onClick={() => run()} />
              <button id="cancel" onClick={() => cancel()} />
            </div>
          );
        },
      }),
    );
    const run = () => wrapper.find('#run').trigger('click');
    const cancel = () => wrapper.find('#cancel').trigger('click');
    await run();
    // trigger by leading
    expect(mockFn).toHaveBeenCalledTimes(1);
    await waitForTime(10);
    await cancel();

    await run();
    // trigger by leading
    expect(mockFn).toHaveBeenCalledTimes(2);
    await waitForTime(10);
    await cancel();

    await run();
    // trigger by leading
    expect(mockFn).toHaveBeenCalledTimes(3);
    await waitForTime(50);
    await run();
    await run();

    await waitForAll();
    // trigger by trailing
    expect(mockFn).toHaveBeenCalledTimes(4);
  });

  test('cache should work', async () => {
    let count = 0;
    const TestComponent = defineComponent({
      setup() {
        const { data, run } = useRequest(request, {
          cacheKey: 'cacheKey',
          cacheTime: 10000,
        });
        return () => (
          <button onClick={() => run((count += 1))}>{data.value}</button>
        );
      },
    });

    let wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('');
    await waitForTime(1000);
    expect(wrapper.find('button').text()).toBe('success');
    for (let index = 0; index < 5; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(1000);
    }
    expect(wrapper.find('button').text()).toBe('5');
    wrapper.unmount();

    // remount component
    wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('5');
    await waitForTime(1000);
    expect(wrapper.find('button').text()).toBe('5');
    for (let index = 0; index < 5; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(1000);
    }
    expect(wrapper.find('button').text()).toBe('10');
    wrapper.unmount();
    // waiting for cache timeout
    waitForTime(10000);

    // remount component
    wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('');
  });

  test('cache staleTime should work', async () => {
    let count = 0;
    const TestComponent = defineComponent({
      setup() {
        const { data, run } = useRequest(request, {
          cacheKey: 'cacheKey',
          staleTime: 5000,
        });
        return () => (
          <button onClick={() => run((count += 1))}>{data.value}</button>
        );
      },
    });
    let wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('');
    await waitForTime(1000);
    expect(wrapper.find('button').text()).toBe('success');
    for (let index = 0; index < 5; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(1000);
    }
    expect(wrapper.find('button').text()).toBe('5');
    wrapper.unmount();

    // remount component
    wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('5');
    await waitForTime(1000);
    expect(wrapper.find('button').text()).toBe('5');
    for (let index = 0; index < 5; index++) {
      await wrapper.find('button').trigger('click');
      await waitForTime(1000);
    }
    expect(wrapper.find('button').text()).toBe('10');
    wrapper.unmount();
    // waiting for stale timeout
    jest.setSystemTime(new Date().getTime() + 5000);

    // remount component
    wrapper = shallowMount(TestComponent);
    expect(wrapper.find('button').text()).toBe('10');
    await waitForTime(1000);
    expect(wrapper.find('button').text()).toBe('10');
  });

  test('queryKey should work : case 1', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          // auto run with empty params
          const { loading, params, data } = useRequest(request, {
            queryKey: id => id,
          });
          return () => (
            <div>
              <div id="loading">{`${loading.value}`}</div>
              <div id="data">{`${data.value}`}</div>
              <div id="params">{`${params.value.length}`}</div>
            </div>
          );
        },
      }),
    );

    expect(wrapper.find('#loading').text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.find('#loading').text()).toBe('false');
    expect(wrapper.find('#data').text()).toBe('success');
    expect(wrapper.find('#params').text()).toBe('0');
  });

  test('queryKey should work : case 2', async () => {
    const users = [
      { id: '1', username: 'A' },
      { id: '2', username: 'B' },
      { id: '3', username: 'C' },
    ];

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, queries, data, loading } = useRequest(request, {
            manual: true,
            queryKey: id => id,
          });

          return () => (
            <div>
              <div id="data">{data.value}</div>
              <div id="loading">{loading.value.toString()}</div>
              <ul>
                {users.map(item => (
                  <li
                    key={item.id}
                    id={item.username}
                    onClick={() => run(item.id)}
                  >
                    {queries[item.id]?.loading ? 'loading' : item.username}
                  </li>
                ))}
              </ul>
            </div>
          );
        },
      }),
    );

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;

      await wrapper.find(`#${userName}`).trigger('click');
      expect(wrapper.find(`#${userName}`).text()).toBe('loading');

      expect(wrapper.find('#data').text()).toBe('');
      expect(wrapper.find('#loading').text()).toBe('true');

      await waitForTime(1000);
      expect(wrapper.find(`#${userName}`).text()).toBe(userName);

      expect(wrapper.find('#data').text()).toBe(currentId);
      expect(wrapper.find('#loading').text()).toBe('false');
    }
  });

  test('queryKey should work : case 3', async () => {
    // swr
    const users = [
      { id: '1', username: 'A' },
      { id: '2', username: 'B' },
      { id: '3', username: 'C' },
    ];

    const Child = defineComponent({
      setup() {
        const { run, queries } = useRequest(request, {
          queryKey: id => id,
          cacheKey: 'users',
        });

        return () => (
          <div>
            <ul id="child">
              {users.map(item => (
                <li
                  key={item.id}
                  id={item.username}
                  onClick={() => run(item.id)}
                >
                  {queries[item.id]?.loading ? 'loading' : item.username}
                </li>
              ))}
            </ul>
          </div>
        );
      },
    });

    const Parent = mount(
      defineComponent({
        props: {
          show: {
            type: Boolean,
            default: false,
          },
        },
        setup(props) {
          return () => <div>{props.show && <Child />}</div>;
        },
      }),
    );

    await Parent.setProps({
      show: true,
    });

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;

      await Parent.find(`#${userName}`).trigger('click');
      expect(Parent.find(`#${userName}`).text()).toBe('loading');
      await waitForTime(1000);
      expect(Parent.find(`#${userName}`).text()).toBe(userName);
    }

    // unmount Child
    await Parent.setProps({
      show: false,
    });

    // remount Child
    await Parent.setProps({
      show: true,
    });

    // all queries will auto refresh
    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;

      expect(Parent.find(`#${userName}`).text()).toBe('loading');
    }

    await waitForTime(1000);

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;

      expect(Parent.find(`#${userName}`).text()).toBe(userName);
    }
  });

  test('queryKey should work : case 4', async () => {
    const users = [
      { id: '1', username: 'A' },
      { id: '2', username: 'B' },
      { id: '3', username: 'C' },
    ];

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, queries, data, loading } = useRequest(request, {
            manual: true,
            refreshOnWindowFocus: true,
            queryKey: id => id,
          });

          return () => (
            <div>
              <div id="data">{data.value}</div>
              <div id="loading">{loading.value.toString()}</div>
              <ul>
                {users.map(item => (
                  <li
                    key={item.id}
                    id={item.username}
                    onClick={() => run(item.id)}
                  >
                    {queries[item.id]?.loading
                      ? 'loading'
                      : queries[item.id]?.data}
                  </li>
                ))}
              </ul>
            </div>
          );
        },
      }),
    );

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;

      await wrapper.find(`#${userName}`).trigger('click');
      expect(wrapper.find(`#${userName}`).text()).toBe('loading');

      expect(wrapper.find('#data').text()).toBe('');
      expect(wrapper.find('#loading').text()).toBe('true');

      await waitForTime(1000);
      expect(wrapper.find(`#${userName}`).text()).toBe(currentId);

      expect(wrapper.find('#data').text()).toBe(currentId);
      expect(wrapper.find('#loading').text()).toBe('false');
    }

    // mock tab visible
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    await waitForTime(1);
    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      expect(wrapper.find(`#${userName}`).text()).toBe('loading');
    }
    await waitForTime(1000);
    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;
      expect(wrapper.find(`#${userName}`).text()).toBe(currentId);
    }
  });

  test('queryKey should work with root level `cancel`, `mutate`, `refresh`', async () => {
    const users = [
      { id: '1', username: 'A' },
      { id: '2', username: 'B' },
      { id: '3', username: 'C' },
    ];

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, queries, mutate, refresh, cancel } = useRequest(
            request,
            {
              manual: true,
              refreshOnWindowFocus: true,
              queryKey: id => id,
            },
          );

          return () => (
            <div>
              <div id="mutate" onClick={() => mutate('new data')} />
              <div id="refresh" onClick={() => refresh()} />
              <div id="cancel" onClick={() => cancel()} />
              <ul>
                {users.map(item => (
                  <li
                    key={item.id}
                    id={item.username}
                    onClick={() => run(item.id)}
                  >
                    {queries[item.id]?.loading
                      ? 'loading'
                      : queries[item.id]?.data}
                  </li>
                ))}
              </ul>
            </div>
          );
        },
      }),
    );

    const mutate = () => wrapper.find('#mutate').trigger('click');
    const refresh = () => wrapper.find('#refresh').trigger('click');
    const cancel = () => wrapper.find('#cancel').trigger('click');

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;
      const userElement = wrapper.find(`#${userName}`);
      await userElement.trigger('click');
      expect(userElement.text()).toBe('loading');
      await waitForTime(1000);
      expect(userElement.text()).toBe(currentId);

      await mutate();
      expect(userElement.text()).toBe('new data');

      await userElement.trigger('click');
      expect(userElement.text()).toBe('loading');
      await waitForTime(100);
      await cancel();
      expect(userElement.text()).toBe('new data');

      await refresh();
      expect(userElement.text()).toBe('loading');
    }
  });

  test('errorRetry should work. case 1', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, loading } = useRequest(failedRequest, {
            manual: true,
            errorRetryCount: 2,
            errorRetryInterval: 1000,
          });
          const handleClick = () => run();
          return () => (
            <button onClick={handleClick}>{`${loading.value}`}</button>
          );
        },
      }),
    );

    for (let oIndex = 0; oIndex < 10; oIndex++) {
      await wrapper.find('button').trigger('click');
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('false');

      // retrying
      for (let index = 0; index < 2; index++) {
        await waitForTime(1000);
        expect(wrapper.text()).toBe('true');
        await waitForTime(1000);
        expect(wrapper.text()).toBe('false');
      }

      // stop retry
      await waitForTime(1000);
      expect(wrapper.text()).toBe('false');
    }
  });

  test('errorRetry should work. case 2', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, cancel } = useRequest(failedRequest, {
            errorRetryCount: 3,
            errorRetryInterval: 1000,
          });
          return () => (
            <button onClick={() => cancel()}>{`${loading.value}`}</button>
          );
        },
      }),
    );
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('false');
    // first retry
    await waitForTime(1000);
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('false');

    // second retry
    await waitForTime(1000);
    expect(wrapper.text()).toBe('true');

    // trigger cancel
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('false');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('false');
  });

  test('errorRetry should work. case 3', async () => {
    const mockFn = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, loading } = useRequest(failedRequest, {
            manual: true,
            errorRetryCount: 10,
            onError: () => mockFn(),
          });
          const handleClick = () => run();
          return () => (
            <button onClick={handleClick}>{`${loading.value}`}</button>
          );
        },
      }),
    );

    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('false');

    // retrying
    for (let index = 0; index < 10; index++) {
      await waitForAll();
      expect(wrapper.text()).toBe('false');
    }

    // stop retry
    await waitForAll();
    expect(wrapper.text()).toBe('false');
    expect(mockFn).toHaveBeenCalledTimes(11);
  });

  test('errorRetry should work with debounce', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, loading, error } = useRequest(failedRequest, {
            manual: true,
            debounceInterval: 1000,
            errorRetryCount: 4,
            errorRetryInterval: 1000,
          });
          const handleClick = () => run();
          return () => (
            <button onClick={handleClick}>
              {`${loading.value || error.value?.message}`}
            </button>
          );
        },
      }),
    );

    // request
    await wrapper.find('button').trigger('click');
    await waitForTime(2001);
    expect(wrapper.text()).toBe('fail');

    // retrying 1
    await waitForTime(1000);
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('fail');

    // retrying 2
    await waitForTime(1000);
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('fail');

    // trigger button to reset retry count
    await wrapper.find('button').trigger('click');
    await waitForTime(2001);
    expect(wrapper.text()).toBe('fail');

    for (let index = 0; index < 4; index++) {
      await waitForTime(1000);
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('fail');
    }
    await waitForTime(1000);
    expect(wrapper.text()).toBe('fail');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('fail');
  });

  test('errorRetry should work with pollingInterval', async () => {
    let flag = true;
    const mixinRequest = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (flag) {
            resolve('success');
          } else {
            reject(new Error('fail'));
          }
        }, 1000);
      });
    };
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, error, data } = useRequest(mixinRequest, {
            errorRetryCount: 3,
            errorRetryInterval: 600,
            pollingInterval: 500,
          });
          return () => (
            <button>
              {`${loading.value || data.value || error.value?.message}`}
            </button>
          );
        },
      }),
    );

    // normal API request
    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('success');
      await waitForTime(500);
    }

    // mock API error request
    flag = false;

    // retrying
    for (let index = 0; index < 3; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('fail');
      await waitForTime(600);
    }

    // stop retry
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe('fail');
    await waitForTime(600);
    expect(wrapper.text()).toBe('fail');
  });

  test('pollingInterval always receive a error request', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, error } = useRequest(failedRequest, {
            pollingInterval: 1000,
          });
          return () => (
            <button>{`${loading.value || error.value?.message}`}</button>
          );
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('fail');
      await waitForTime(1000);
    }
  });

  test('pollingInterval always receive a error request and errorRetryCount is -1', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, error } = useRequest(failedRequest, {
            errorRetryCount: -1,
            pollingInterval: 500,
            errorRetryInterval: 600,
          });
          return () => (
            <button>{`${loading.value || error.value?.message}`}</button>
          );
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe('fail');
      await waitForTime(600);
    }
  });

  test('reset loadingDelay correctly when rerun or refresh', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { loading, run, refresh } = useRequest(request, {
            loadingDelay: 500,
          });
          return () => (
            <div>
              <div id="loading">{`${loading.value}`}</div>
              <button
                id="run"
                onClick={() => {
                  run();
                }}
              />
              <button
                id="refresh"
                onClick={() => {
                  refresh();
                }}
              />
            </div>
          );
        },
      }),
    );
    const loadingRes = () => wrapper.find('#loading').text();
    const run = () => wrapper.find('#run').trigger('click');
    const refresh = () => wrapper.find('#refresh').trigger('click');
    await waitForTime(300);
    expect(loadingRes()).toBe('false');

    run();
    await waitForTime(300);
    expect(loadingRes()).toBe('false');
    await waitForTime(200);
    expect(loadingRes()).toBe('true');

    refresh();
    await waitForTime(300);
    expect(loadingRes()).toBe('false');
    await waitForTime(200);
    expect(loadingRes()).toBe('true');
  });

  test('reset polling correctly when rerun or refresh', async () => {
    enum RequestType {
      run,
      refresh,
      polling,
    }
    const requestTypeRef = ref<RequestType>(RequestType.run);

    const runCountRef = ref(0);
    const refreshCountRef = ref(0);
    const pollingCountRef = ref(0);

    const expectCount = (param: Ref<number>, value: number) => {
      expect(param.value).toBe(value);
    };

    const triggerWithCorrectType = (source: Function, type: RequestType) => {
      requestTypeRef.value = type;
      source();
    };
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, refresh } = useRequest(
            () => {
              switch (requestTypeRef.value) {
                case RequestType.polling:
                  pollingCountRef.value += 1;
                  break;
                case RequestType.run:
                  runCountRef.value += 1;
                  break;
                case RequestType.refresh:
                  refreshCountRef.value += 1;
                  break;
              }

              if (
                requestTypeRef.value === RequestType.run ||
                requestTypeRef.value === RequestType.refresh
              ) {
                requestTypeRef.value = RequestType.polling;
              }

              return request();
            },
            {
              pollingInterval: 500,
            },
          );
          return () => (
            <div>
              <button
                id="run"
                onClick={() => {
                  run();
                }}
              />
              <button
                id="refresh"
                onClick={() => {
                  refresh();
                }}
              />
            </div>
          );
        },
      }),
    );

    const run = () => wrapper.find('#run').trigger('click');
    const refresh = () => wrapper.find('#refresh').trigger('click');
    /* ------------------------------------- run ------------------------------------- */

    expectCount(runCountRef, 1);
    expectCount(pollingCountRef, 0);

    // auto run
    await waitForTime(1000);

    for (let index = 1; index <= 100; index++) {
      // wait for polling
      await waitForTime(500);

      // request complete
      await waitForTime(1000);
      expectCount(runCountRef, 1);
      expectCount(pollingCountRef, index);
    }

    // polling is pending
    await waitForTime(200);

    triggerWithCorrectType(run, RequestType.run);
    await waitForTime(1000);

    expectCount(runCountRef, 2);
    expectCount(pollingCountRef, 100);

    for (let index = 1; index <= 100; index++) {
      // wait for polling
      await waitForTime(500);

      // request complete
      await waitForTime(1000);
      expectCount(pollingCountRef, index + 100);
    }

    /* ------------------------------------- refresh ------------------------------------- */
    expectCount(runCountRef, 2);
    expectCount(refreshCountRef, 0);
    expectCount(pollingCountRef, 200);

    // polling is pending
    await waitForTime(200);

    triggerWithCorrectType(refresh, RequestType.refresh);

    expectCount(refreshCountRef, 1);
    expectCount(pollingCountRef, 200);

    // refresh complete
    await waitForTime(1000);

    for (let index = 1; index <= 100; index++) {
      // wait for polling
      await waitForTime(500);

      // request complete
      await waitForTime(1000);
      expectCount(pollingCountRef, index + 200);
    }

    expectCount(runCountRef, 2);
    expectCount(refreshCountRef, 1);
    expectCount(pollingCountRef, 300);
  });

  test('reset error retry correctly when rerun or refresh', async () => {
    enum RequestType {
      run,
      refresh,
      errorRetry,
    }
    const requestTypeRef = ref<RequestType>(RequestType.run);

    const runCountRef = ref(0);
    const refreshCountRef = ref(0);
    const errorRetryCountRef = ref(0);

    const expectCount = (param: Ref<number>, value: number) => {
      expect(param.value).toBe(value);
    };

    const triggerWithCorrectType = (source: Function, type: RequestType) => {
      requestTypeRef.value = type;
      source();
    };

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, refresh, error } = useRequest(
            () => {
              switch (requestTypeRef.value) {
                case RequestType.errorRetry:
                  errorRetryCountRef.value += 1;
                  break;
                case RequestType.run:
                  runCountRef.value += 1;
                  break;
                case RequestType.refresh:
                  refreshCountRef.value += 1;
                  break;
              }

              if (
                requestTypeRef.value === RequestType.run ||
                requestTypeRef.value === RequestType.refresh
              ) {
                requestTypeRef.value = RequestType.errorRetry;
              }

              return failedRequest();
            },
            {
              errorRetryCount: 5,
              errorRetryInterval: 500,
            },
          );
          return () => (
            <div>
              <div id="error">{`${error.value?.message}`}</div>
              <button
                id="run"
                onClick={() => {
                  run();
                }}
              />
              <button
                id="refresh"
                onClick={() => {
                  refresh();
                }}
              />
            </div>
          );
        },
      }),
    );
    const errorRes = () => wrapper.find('#error').text();
    const run = () => wrapper.find('#run').trigger('click');
    const refresh = () => wrapper.find('#refresh').trigger('click');
    /* ------------------------------------- run ------------------------------------- */
    expectCount(runCountRef, 1);
    expectCount(errorRetryCountRef, 0);
    expect(errorRes()).toBe('undefined');

    // wait for request
    await waitForTime(1000);

    // receive a error result
    expect(errorRes()).not.toBe('undefined');
    // wait for error retry
    await waitForTime(500);

    expectCount(runCountRef, 1);
    expectCount(errorRetryCountRef, 1);

    await waitForTime(1000);
    // error retry is pending
    await waitForTime(300);

    triggerWithCorrectType(run, RequestType.run);
    expectCount(runCountRef, 2);
    expectCount(errorRetryCountRef, 1);

    await waitForTime(1000);
    await waitForTime(500);

    expectCount(runCountRef, 2);
    expectCount(errorRetryCountRef, 2);

    /* ------------------------------------- refresh ------------------------------------- */
    await waitForTime(1000);

    expectCount(runCountRef, 2);
    expectCount(errorRetryCountRef, 2);

    triggerWithCorrectType(refresh, RequestType.refresh);
    expectCount(refreshCountRef, 1);
    expectCount(errorRetryCountRef, 2);

    await waitForTime(1000);
    await waitForTime(500);

    expectCount(refreshCountRef, 1);
    expectCount(errorRetryCountRef, 3);

    await waitForTime(1000);
    // error retry is pending
    await waitForTime(300);

    triggerWithCorrectType(refresh, RequestType.refresh);
    expectCount(refreshCountRef, 2);
    expectCount(errorRetryCountRef, 3);

    // receive a error result
    await waitForTime(1000);

    // start error retry
    for (let index = 0; index < 100; index++) {
      await waitForTime(1000);
      await waitForTime(500);
    }

    expectCount(runCountRef, 2);
    expectCount(refreshCountRef, 2);
    // 5 times is the retry count
    expectCount(errorRetryCountRef, 3 + 5);
  });

  test('pollingWhenOffline should work. case 1', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, loading } = useRequest(() => request((count += 1)), {
            pollingInterval: 500,
          });
          return () => <button>{`${loading.value || data.value}`}</button>;
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${index + 1}`);
      await waitForTime(500);
    }

    // mock offline
    Object.defineProperty(window.navigator, 'onLine', {
      value: false,
      writable: true,
    });

    // last request
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe(`1001`);
    await waitForTime(500);
    expect(wrapper.text()).toBe(`1001`);

    // mock online
    Object.defineProperty(window.navigator, 'onLine', {
      value: true,
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('online'));
    await waitForTime(1);

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${1001 + index + 1}`);
      await waitForTime(500);
    }
  });

  test('pollingWhenOffline should work. case 2', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, loading } = useRequest(() => request((count += 1)), {
            pollingInterval: 500,
            pollingWhenOffline: true,
          });
          return () => <button>{`${loading.value || data.value}`}</button>;
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${index + 1}`);
      await waitForTime(500);
    }

    // mock offline
    Object.defineProperty(window.navigator, 'onLine', {
      value: false,
      writable: true,
    });

    // last request
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe(`1001`);
    await waitForTime(500);
    expect(wrapper.text()).toBe(`true`);

    // mock online
    Object.defineProperty(window.navigator, 'onLine', {
      value: true,
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('online'));
    await waitForTime(1);

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${1001 + index + 1}`);
      await waitForTime(500);
    }
  });

  test('pollingWhenOffline should work with pollingWhenHidden', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, loading } = useRequest(() => request((count += 1)), {
            pollingInterval: 500,
          });
          return () => <button>{`${loading.value || data.value}`}</button>;
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${index + 1}`);
      await waitForTime(500);
    }

    // mock offline
    Object.defineProperty(window.navigator, 'onLine', {
      value: false,
      writable: true,
    });

    // last request
    expect(wrapper.text()).toBe('true');
    await waitForTime(1000);
    expect(wrapper.text()).toBe(`1001`);
    await waitForTime(500);
    expect(wrapper.text()).toBe(`1001`);

    // mock tab show
    Object.defineProperty(document, 'visibilityState', {
      value: 'visible',
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('visibilitychange'));
    // wait 1ms make to sure event has trigger
    await waitForTime(1);
    expect(wrapper.text()).toBe(`1001`);

    // mock online
    Object.defineProperty(window.navigator, 'onLine', {
      value: true,
      writable: true,
    });
    jsdom.window.dispatchEvent(new Event('online'));
    // wait 1ms to make sure event has trigger
    await waitForTime(1);

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${1001 + index + 1}`);
      await waitForTime(500);
    }
  });

  test('listener should unsubscribe when the component was unmounted', async () => {
    let count = 0;
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, loading } = useRequest(() => request((count += 1)), {
            pollingInterval: 500,
          });
          return () => <button>{`${loading.value || data.value}`}</button>;
        },
      }),
    );

    for (let index = 0; index < 1000; index++) {
      expect(wrapper.text()).toBe('true');
      await waitForTime(1000);
      expect(wrapper.text()).toBe(`${index + 1}`);
      await waitForTime(500);
    }

    expect(RECONNECT_LISTENER.size).toBe(1);
    wrapper.unmount();
    expect(RECONNECT_LISTENER.size).toBe(0);
  });

  test('global options should work', async () => {
    const ComponentA = defineComponent({
      setup() {
        const { data, run } = useRequest(request);
        return () => <button onClick={() => run()}>{data.value}</button>;
      },
    });
    const ComponentB = defineComponent({
      setup() {
        const { data, run } = useRequest(request);
        return () => <button onClick={() => run()}>{data.value}</button>;
      },
    });

    setGlobalOptions({ manual: true });
    let wrapperA = shallowMount(ComponentA);
    let wrapperB = shallowMount(ComponentB);

    expect(wrapperA.find('button').text()).toBe('');
    expect(wrapperB.find('button').text()).toBe('');
    await waitForTime(1000);
    expect(wrapperA.find('button').text()).toBe('');
    expect(wrapperB.find('button').text()).toBe('');
    await wrapperA.find('button').trigger('click');
    await wrapperB.find('button').trigger('click');
    await waitForTime(1000);
    expect(wrapperA.find('button').text()).toBe('success');
    expect(wrapperB.find('button').text()).toBe('success');

    // clear global options
    clearGlobalOptions();
    wrapperA = shallowMount(ComponentA);
    wrapperB = shallowMount(ComponentB);

    expect(wrapperA.find('button').text()).toBe('');
    expect(wrapperB.find('button').text()).toBe('');
    await waitForTime(1000);
    expect(wrapperA.find('button').text()).toBe('success');
    expect(wrapperB.find('button').text()).toBe('success');
  });

  test('RequestConfig should work', async () => {
    const createComponent = (id: string, requestOptions: GlobalOptions = {}) =>
      defineComponent({
        setup() {
          const { loading, run } = useRequest(request, requestOptions);

          return () => (
            <button id={id} onClick={run}>
              {`${loading.value}`}
            </button>
          );
        },
      });

    const ComponentA = createComponent('A');
    const ComponentB = createComponent('B');
    const ComponentC = createComponent('C');
    const ComponentD = createComponent('D');
    const ComponentE = createComponent('E', { loadingDelay: 800 });

    setGlobalOptions({
      manual: true,
      loadingDelay: 500,
    });

    const Wrapper = defineComponent({
      setup() {
        return () => (
          <div id="root">
            <RequestConfig config={{ loadingDelay: 0 }}>
              <ComponentA />
            </RequestConfig>

            <RequestConfig config={{ manual: false }}>
              <ComponentB />

              <ComponentE />

              {/* nested */}
              <RequestConfig config={{ manual: true, loadingDelay: 200 }}>
                <ComponentC />
              </RequestConfig>
            </RequestConfig>

            <ComponentD />
          </div>
        );
      },
    });

    const wrapperA = mount(Wrapper);

    expect(wrapperA.find('#A').text()).toBe('false');
    expect(wrapperA.find('#B').text()).toBe('false');
    expect(wrapperA.find('#C').text()).toBe('false');
    expect(wrapperA.find('#D').text()).toBe('false');
    expect(wrapperA.find('#E').text()).toBe('false');

    await wrapperA.find('#A').trigger('click');
    await wrapperA.find('#C').trigger('click');
    await wrapperA.find('#D').trigger('click');

    expect(wrapperA.find('#A').text()).toBe('true');
    expect(wrapperA.find('#B').text()).toBe('false');
    expect(wrapperA.find('#C').text()).toBe('false');
    expect(wrapperA.find('#D').text()).toBe('false');
    expect(wrapperA.find('#E').text()).toBe('false');

    await waitForTime(200);

    expect(wrapperA.find('#A').text()).toBe('true');
    expect(wrapperA.find('#B').text()).toBe('false');
    expect(wrapperA.find('#C').text()).toBe('true');
    expect(wrapperA.find('#D').text()).toBe('false');
    expect(wrapperA.find('#E').text()).toBe('false');

    await waitForTime(300);

    expect(wrapperA.find('#A').text()).toBe('true');
    expect(wrapperA.find('#B').text()).toBe('true');
    expect(wrapperA.find('#C').text()).toBe('true');
    expect(wrapperA.find('#D').text()).toBe('true');
    expect(wrapperA.find('#E').text()).toBe('false');

    await waitForTime(300);

    expect(wrapperA.find('#A').text()).toBe('true');
    expect(wrapperA.find('#B').text()).toBe('true');
    expect(wrapperA.find('#C').text()).toBe('true');
    expect(wrapperA.find('#D').text()).toBe('true');
    expect(wrapperA.find('#E').text()).toBe('true');

    await waitForTime(200);

    expect(wrapperA.find('#A').text()).toBe('false');
    expect(wrapperA.find('#B').text()).toBe('false');
    expect(wrapperA.find('#C').text()).toBe('false');
    expect(wrapperA.find('#D').text()).toBe('false');
    expect(wrapperA.find('#E').text()).toBe('false');

    wrapperA.unmount();

    // clear global options
    clearGlobalOptions();
    const wrapperB = mount(Wrapper);

    expect(wrapperB.find('#A').text()).toBe('true');
    expect(wrapperB.find('#B').text()).toBe('true');
    expect(wrapperB.find('#C').text()).toBe('false');
    expect(wrapperB.find('#D').text()).toBe('true');
    expect(wrapperB.find('#E').text()).toBe('false');

    await wrapperB.find('#C').trigger('click');

    await waitForTime(200);

    expect(wrapperB.find('#A').text()).toBe('true');
    expect(wrapperB.find('#B').text()).toBe('true');
    expect(wrapperB.find('#C').text()).toBe('true');
    expect(wrapperB.find('#D').text()).toBe('true');
    expect(wrapperB.find('#E').text()).toBe('false');

    await waitForTime(600);

    expect(wrapperB.find('#A').text()).toBe('true');
    expect(wrapperB.find('#B').text()).toBe('true');
    expect(wrapperB.find('#C').text()).toBe('true');
    expect(wrapperB.find('#D').text()).toBe('true');
    expect(wrapperB.find('#E').text()).toBe('true');

    await waitForTime(200);

    expect(wrapperB.find('#A').text()).toBe('false');
    expect(wrapperB.find('#B').text()).toBe('false');
    expect(wrapperB.find('#C').text()).toBe('false');
    expect(wrapperB.find('#D').text()).toBe('false');
    expect(wrapperB.find('#E').text()).toBe('false');
  });

  test('reload should work: case 1', async () => {
    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, reload, reloading, data } = useRequest(request, {
            defaultParams: ['hello'],
          });
          return () => (
            <div>
              <div class="reloading">{`${reloading.value}`}</div>
              <button class="run" onClick={() => run('hi there')}></button>
              <button class="reload" onClick={() => reload()}></button>
              <div class="data">{data.value}</div>
            </div>
          );
        },
      }),
    );

    const dataEl = wrapper.find('.data');
    const runEl = wrapper.find('.run');
    const reloadingEl = wrapper.find('.reloading');
    const reloadEl = wrapper.find('.reload');

    expect(reloadingEl.text()).toBe('false');
    await waitForTime(1000);
    expect(reloadingEl.text()).toBe('false');
    expect(dataEl.text()).toBe('hello');

    await runEl.trigger('click');
    expect(reloadingEl.text()).toBe('false');
    await waitForTime(1000);
    expect(reloadingEl.text()).toBe('false');
    expect(dataEl.text()).toEqual('hi there');

    await reloadEl.trigger('click');
    expect(reloadingEl.text()).toBe('true');
    await waitForTime(1000);
    expect(reloadingEl.text()).toBe('false');
    expect(dataEl.text()).toEqual('hello');

    await runEl.trigger('click');
    expect(reloadingEl.text()).toBe('false');
    await waitForTime(1000);
    expect(reloadingEl.text()).toBe('false');
    expect(dataEl.text()).toEqual('hi there');
  });

  test('reload should work: case 2', async () => {
    const users = [
      { id: '1', username: 'A' },
      { id: '2', username: 'B' },
      { id: '3', username: 'C' },
    ];

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { run, queries, data, loading, reload } = useRequest(request, {
            manual: true,
            refreshOnWindowFocus: true,
            queryKey: id => id,
          });

          return () => (
            <div>
              <div id="data">{data.value}</div>
              <div id="loading">{loading.value.toString()}</div>
              <div id="reload" onClick={() => reload()} />
              <ul>
                {users.map(item => (
                  <li
                    key={item.id}
                    id={item.username}
                    onClick={() => run(item.id)}
                  >
                    {queries[item.id]?.loading
                      ? 'loading'
                      : queries[item.id]?.data}
                  </li>
                ))}
              </ul>
            </div>
          );
        },
      }),
    );

    const dataEl = wrapper.find('#data');
    const loadingEl = wrapper.find('#loading');
    const reloadEl = wrapper.find('#reload');

    expect(FOCUS_LISTENER.size).toBe(1);
    expect(VISIBLE_LISTENER.size).toBe(2);
    expect(RECONNECT_LISTENER.size).toBe(1);

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;

      await wrapper.find(`#${userName}`).trigger('click');
      expect(wrapper.find(`#${userName}`).text()).toBe('loading');

      expect(dataEl.text()).toBe('');
      expect(loadingEl.text()).toBe('true');

      await waitForTime(1000);
      expect(wrapper.find(`#${userName}`).text()).toBe(currentId);

      expect(dataEl.text()).toBe(currentId);
      expect(loadingEl.text()).toBe('false');
    }

    expect(FOCUS_LISTENER.size).toBe(4);
    expect(VISIBLE_LISTENER.size).toBe(8);
    expect(RECONNECT_LISTENER.size).toBe(4);

    await reloadEl.trigger('click');
    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      expect(wrapper.find(`#${userName}`).text()).toBe('');
      expect(dataEl.text()).toBe('');
    }

    expect(FOCUS_LISTENER.size).toBe(1);
    expect(VISIBLE_LISTENER.size).toBe(2);
    expect(RECONNECT_LISTENER.size).toBe(1);

    for (let i = 0; i < users.length; i++) {
      const userName = users[i].username;
      const currentId = users[i].id;

      await wrapper.find(`#${userName}`).trigger('click');
      expect(wrapper.find(`#${userName}`).text()).toBe('loading');

      expect(dataEl.text()).toBe('');
      expect(loadingEl.text()).toBe('true');

      await waitForTime(1000);
      expect(wrapper.find(`#${userName}`).text()).toBe(currentId);

      expect(dataEl.text()).toBe(currentId);
      expect(loadingEl.text()).toBe('false');
    }

    expect(FOCUS_LISTENER.size).toBe(4);
    expect(VISIBLE_LISTENER.size).toBe(8);
    expect(RECONNECT_LISTENER.size).toBe(4);
  });

  test('onBefore and onAfter hooks can use', async () => {
    const onBefore = jest.fn();
    const onAfter = jest.fn();

    const wrapper = shallowMount(
      defineComponent({
        setup() {
          const { data, run } = useRequest(request, {
            onBefore,
            onAfter,
          });

          return () => (
            <button onClick={() => run()}>{`data:${data.value}`}</button>
          );
        },
      }),
    );
    expect(onBefore).toHaveBeenCalledTimes(1);
    expect(onAfter).toHaveBeenCalledTimes(0);
    await waitForTime(100);
    expect(onBefore).toHaveBeenCalledTimes(1);
    expect(onAfter).toHaveBeenCalledTimes(0);
    await waitForTime(800);
    expect(onBefore).toHaveBeenCalledTimes(1);
    expect(onAfter).toHaveBeenCalledTimes(0);
    await waitForTime(100);
    expect(onBefore).toHaveBeenCalledTimes(1);
    expect(onAfter).toHaveBeenCalledTimes(1);
  });
});
Example #24
Source File: useAsyncQuery.ts    From vue-request with MIT License 4 votes vote down vote up
function useAsyncQuery<R, P extends unknown[]>(
  query: Query<R, P>,
  options: BaseOptions<R, P>,
): BaseResult<R, P> {
  const injectedGlobalOptions = inject<GlobalOptions>(
    GLOBAL_OPTIONS_PROVIDE_KEY,
    {},
  );

  const {
    cacheKey,
    defaultParams = ([] as unknown) as P,
    manual = false,
    ready = ref(true),
    refreshDeps = [],
    loadingDelay = 0,
    pollingWhenHidden = false,
    pollingWhenOffline = false,
    refreshOnWindowFocus = false,
    refocusTimespan = 5000,
    cacheTime = 600000,
    staleTime = 0,
    errorRetryCount = 0,
    errorRetryInterval = 0,
    queryKey,
    ...rest
  } = {
    ...getGlobalOptions(),
    ...injectedGlobalOptions,
    ...options,
  };

  const stopPollingWhenHiddenOrOffline = ref(false);
  // skip debounce when initail run
  const initialAutoRunFlag = ref(false);

  const updateCache = (state: State<R, P>) => {
    if (!cacheKey) return;

    const cacheData = getCache<R, P>(cacheKey)?.data;
    const cacheQueries = cacheData?.queries;
    const queryData = unRefObject(state);
    const currentQueryKey =
      queryKey?.(...state.params.value) ?? QUERY_DEFAULT_KEY;

    setCache<R, P>(
      cacheKey,
      {
        queries: {
          ...cacheQueries,
          [currentQueryKey]: {
            ...cacheQueries?.[currentQueryKey],
            ...queryData,
          },
        },
        latestQueriesKey: currentQueryKey,
      },
      cacheTime,
    );
  };

  const config = {
    initialAutoRunFlag,
    loadingDelay,
    pollingWhenHidden,
    pollingWhenOffline,
    stopPollingWhenHiddenOrOffline,
    cacheKey,
    errorRetryCount,
    errorRetryInterval,
    refreshOnWindowFocus,
    refocusTimespan,
    updateCache,
    ...omit(rest, ['pagination', 'listKey']),
  } as Config<R, P>;

  const loading = ref(false);
  const data = ref<R>();
  const error = ref<Error>();
  const params = ref() as Ref<P>;

  const queries = <Queries<R, P>>reactive({
    [QUERY_DEFAULT_KEY]: reactive(createQuery(query, config)),
  });

  const latestQueriesKey = ref(QUERY_DEFAULT_KEY);

  const latestQuery = computed(() => queries[latestQueriesKey.value] ?? {});

  // sync state
  watch(
    latestQuery,
    queryData => {
      loading.value = queryData.loading;
      data.value = queryData.data;
      error.value = queryData.error;
      params.value = queryData.params;
    },
    {
      immediate: true,
      deep: true,
    },
  );

  // init queries from cache
  if (cacheKey) {
    const cache = getCache<R, P>(cacheKey);

    if (cache?.data?.queries) {
      Object.keys(cache.data.queries).forEach(key => {
        const cacheQuery = cache.data.queries![key];

        queries[key] = <UnWrapState<R, P>>reactive(
          createQuery(query, config, {
            loading: cacheQuery.loading,
            params: cacheQuery.params,
            data: cacheQuery.data,
            error: cacheQuery.error,
          }),
        );
      });
      /* istanbul ignore else */
      if (cache.data.latestQueriesKey) {
        latestQueriesKey.value = cache.data.latestQueriesKey;
      }
    }
  }

  const tempReadyParams = ref();
  const hasTriggerReady = ref(false);
  const run = (...args: P) => {
    if (!ready.value && !hasTriggerReady.value) {
      tempReadyParams.value = args;
      return resolvedPromise;
    }

    const newKey = queryKey?.(...args) ?? QUERY_DEFAULT_KEY;

    if (!queries[newKey]) {
      queries[newKey] = <UnWrapState<R, P>>reactive(createQuery(query, config));
    }

    latestQueriesKey.value = newKey;

    return latestQuery.value.run(...args);
  };

  const reset = () => {
    unmountQueries();
    latestQueriesKey.value = QUERY_DEFAULT_KEY;
    queries[QUERY_DEFAULT_KEY] = <UnWrapState<R, P>>(
      reactive(createQuery(query, config))
    );
  };

  // unmount queries
  const unmountQueries = () => {
    Object.keys(queries).forEach(key => {
      queries[key].cancel();
      queries[key].unmount();
      delete queries[key];
    });
  };

  const cancel = () => latestQuery.value.cancel();
  const refresh = () => latestQuery.value.refresh();
  const mutate = <Mutate<R>>((arg: R) => latestQuery.value.mutate(arg));

  // initial run
  if (!manual) {
    initialAutoRunFlag.value = true;

    // TODO: need refactor
    const cache = getCache<R, P>(cacheKey!);
    const cacheQueries = cache?.data.queries ?? {};

    const isFresh =
      cache &&
      (staleTime === -1 || cache.cacheTime + staleTime > new Date().getTime());

    const hasCacheQueries = Object.keys(cacheQueries).length > 0;

    if (!isFresh) {
      if (hasCacheQueries) {
        Object.keys(queries).forEach(key => {
          queries[key]?.refresh();
        });
      } else {
        run(...defaultParams);
      }
    }

    initialAutoRunFlag.value = false;
  }

  // watch ready
  const stopReady = ref();
  stopReady.value = watch(
    ready,
    val => {
      hasTriggerReady.value = true;
      if (val && tempReadyParams.value) {
        run(...tempReadyParams.value);
        // destroy current watch
        stopReady.value();
      }
    },
    {
      flush: 'sync',
    },
  );

  // watch refreshDeps
  if (refreshDeps.length) {
    watch(refreshDeps, () => {
      !manual && latestQuery.value.refresh();
    });
  }

  onUnmounted(() => {
    unmountQueries();
  });

  return {
    loading,
    data,
    error,
    params,
    cancel,
    refresh,
    mutate,
    run,
    reset,
    queries,
  };
}
Example #25
Source File: index.ts    From elenext with MIT License 4 votes vote down vote up
usePopper = (props: UsePopperOptions) => {
  const popperId = uniqueId('el-popper')
  const { referenceRef, popperRef } = props
  const timers: {
    showTimer: any
    hideTimer: any
  } = { showTimer: undefined, hideTimer: undefined }

  const state = reactive<usePopperState>({
    instance: null,
    popperId,
    attrs: {
      styles: {
        popper: {
          position: 'absolute',
          left: '0',
          top: '0',
        },
        arrow: {
          position: 'absolute',
        },
      },
      attributes: {},
    },
  })

  const popperOptions = computed<PopperOptions>(() => {
    return {
      placement: props.placement || 'bottom-start',
      strategy: 'absolute',
      modifiers: [
        {
          name: 'updateState',
          enabled: true,
          phase: 'write',
          fn: ({ state: popperState }: any) => {
            const elements = Object.keys(popperState.elements)
            state.attrs = {
              styles: fromEntries(elements.map(element => [element, popperState.styles[element] || {}])),
              attributes: fromEntries(elements.map(element => [element, popperState.attributes[element] || {}])),
            }
          },
          requires: ['computeStyles'],
        },
        { name: 'applyStyles', enabled: false },
        { name: 'offset', options: { offset: [0, props.offset || 0] } },
      ],
    }
  })

  const clearScheduled = () => {
    clearTimeout(timers.hideTimer)
    clearTimeout(timers.showTimer)
  }

  let clickEvent: any = null

  const togglePopper = (event: MouseEvent) => {
    clickEvent = event
    props.onTrigger(popperId)
  }
  const showPopper = () => {
    clearScheduled()
    timers.showTimer = setTimeout(() => {
      props.onTrigger(popperId, true)
    }, 0)
  }
  const hidePopper = () => {
    clearScheduled()
    timers.hideTimer = setTimeout(() => {
      props.onTrigger(popperId, false)
    }, props.hideDaly || 200)
  }
  const outSideClickHandler = (event: MouseEvent) => {
    // outSideClick 和 togglePopper 冲突
    if (event === clickEvent) {
      return
    }
    if (popperRef.value && !popperRef.value.contains(event.target as Node)) {
      if (
        ['hover', 'focus'].indexOf(props.trigger) !== -1 &&
        referenceRef.value &&
        referenceRef.value.contains(event.target as Node)
      ) {
        return
      } else {
        hidePopper()
      }
    }
  }

  const eventRegOrUnReg = isReg => {
    const referenceEl = referenceRef.value
    const popperEl = popperRef.value
    const event = isReg ? 'addEventListener' : 'removeEventListener'
    if (referenceEl && popperEl) {
      if (props.trigger === 'hover') {
        referenceEl[event]('mouseenter', showPopper)
        referenceEl[event]('mouseleave', hidePopper)
        popperEl[event]('mouseenter', showPopper)
        popperEl[event]('mouseleave', hidePopper)
      }
      if (props.trigger === 'click') {
        referenceEl[event]('click', togglePopper)
        // popperEl[event]('mouseenter', showPopper)
        // popperEl[event]('mouseleave', hidePopper)
      }
      if (props.trigger === 'focus') {
        referenceEl[event]('focus', showPopper)
        referenceEl[event]('blur', hidePopper)
      }
      if (props.trigger !== 'manual') {
        document[event]('click', outSideClickHandler)
      }
    }
  }

  watchEffect(() => {
    if (state.instance) {
      state.instance.setOptions(popperOptions.value)
    }
  })

  watch([referenceRef, popperRef], () => {
    const referenceEl = referenceRef.value
    const popperEl = popperRef.value
    if (referenceEl && popperEl) {
      if (state.instance) {
        state.instance.destroy()
      }
      state.instance = createPopper(referenceEl, popperEl as HTMLElement, popperOptions.value)
    }
  })

  watchEffect(onInvalidate => {
    const referenceEl = referenceRef.value
    const popperEl = popperRef.value
    onInvalidate(() => {
      eventRegOrUnReg(false)
    })
    if (referenceEl && popperEl) {
      eventRegOrUnReg(true)
    }
  })

  onUnmounted(() => {
    if (state.instance) {
      state.instance.destroy()
    }
  })

  return state
}
Example #26
Source File: document.ts    From quantum-sheet with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Create a document
 * @param elementTypes Element types in the document
 */
export function useDocument<TElements extends QuantumDocumentElementTypes<readonly QuantumElementType[]>>(
  elementTypes: TElements
): UseQuantumDocument<TElements> {
  const options = reactive<DocumentOptions>({
    gridCellSize: readonly(new Vector2(20, 20)),
    paperStyle: 'standard',
    paperSize: 'A4',
  })

  const elementRemoveCallbacks = new Map<string, () => void>()
  const elementList = useElementList()
  const elementSelection = useElementSelection()
  const elementFocus = useElementFocus()

  // TODO: Prevent this from being moved
  const rootScope = createElement(ScopeElementType.typeName, {
    position: Vector2.zero,
    size: Vector2.zero,
  })

  function addElement<T extends QuantumElement>(element: T): T {
    // TODO: I think we can use the effectScope API here https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md
    // (Replacing the stopHandles)

    let stopHandles = [elementList.watchElement(element), elementSelection.watchElement(element), elementFocus.watchElement(element)]
    elementRemoveCallbacks.set(element.id, () => {
      stopHandles.forEach((stopHandle) => stopHandle())
    })

    return element
  }

  function createElement<T extends keyof TElements>(typeName: T, options: QuantumElementCreationOptions): GetQuantumElement<TElements[T]> {
    let elementType = elementTypes[typeName]
    if (!elementType) throw new Error(`Unknown element type ${typeName}`)

    const element = new elementType.elementType(options)
    //elementType.useElement(useQuantumElement('' + typeName, options)) as ReturnType<TElements[T]['useElement']>
    addElement(element)

    return element as any
  }

  function deleteElement(element: QuantumElement) {
    let removeCallback = elementRemoveCallbacks.get(element.id)
    if (removeCallback) {
      removeCallback()
      elementRemoveCallbacks.delete(element.id)
    }
  }

  function getElementById<T extends keyof TElements>(id: string, typeName?: T): GetQuantumElement<TElements[T]> | undefined {
    let element = elementList.elements.find((e: QuantumElement) => e.id == id)
    if (element && typeName && element.typeName != typeName) {
      throw new Error(`Wrong type, passed ${typeName} but element has ${element.typeName}`)
    }

    // Yeah, Typescript really does dislike this XD
    return element as any
  }

  function getElementsByType<T extends keyof TElements>(typeName: T): GetQuantumElement<TElements[T]>[] | undefined {
    let elements = elementList.elements.filter((e: QuantumElement) => e.typeName == typeName)

    // Yeah, Typescript really does dislike this XD
    return elements as any[]
  }

  function serializeDocument() {
    let serializedData: SerializedDocument = {
      version: pkg.version,
      options: serializeOptions(options),
      elements: [],
    }
    elementList.elements.forEach((element: QuantumElement) => {
      let elementType = elementTypes[element.typeName]
      serializedData.elements.push(elementType.serializeElement(element))
    })
    return serializedData
  }

  function deserializeDocument(serializedData: SerializedDocument) {
    if (serializedData?.options) {
      const deserializedOptions = deserializeOptions(serializedData?.options)
      options.gridCellSize = deserializedOptions.gridCellSize ?? options.gridCellSize
      options.paperStyle = deserializedOptions.paperStyle ?? options.paperStyle
      options.paperSize = deserializedOptions.paperSize ?? options.paperSize
    }
    serializedData?.elements?.forEach((elementData: JsonType) => {
      let elementType = elementTypes[(elementData as any).typeName]
      if (!elementType) {
        console.warn('Element is missing its type', elementData)
      }
      const { element, onAddedCallback } = elementType.deserializeElement(elementData)
      addElement(element)
      onAddedCallback()
    })
  }

  function moveElements(elements: QuantumElement[], delta: Vector2, limit?: Vector2) {
    // TODO: dont let it move outside sheet (thus no longer needing 'interact.modifiers.restrict'?)
    let limited = false
    elements.forEach((element: QuantumElement) => {
      let newPos = element?.position.value.add(delta)
      if (limit) {
        if (
          newPos.x < 0 ||
          newPos.y < 0 ||
          limit.subtract(newPos.add(element.size.value)).x < 0 ||
          limit.subtract(newPos.add(element.size.value)).y < 0
        ) {
          limited = true
        }
      }
    })
    if (limited) return
    elements.forEach((element: QuantumElement) => {
      let newPos = element?.position.value.add(delta)
      if (newPos) element?.setPosition(newPos)
    })
  }

  function moveSelectedElements(delta: Vector2, limit?: Vector2) {
    moveElements(elementSelection.selectedElements, delta, limit)
  }

  return {
    options,
    elementTypes: elementTypes,
    elements: elementList.elements,
    createElement,
    deleteElement,
    getElementAt: elementList.getElementAt,
    getElementById,
    getSelection: () => [...elementSelection.selectedElements],
    setSelection: elementSelection.setSelection,
    setFocus: elementFocus.setFocus,
    moveElements,
    moveSelectedElements,

    serializeDocument,
    deserializeDocument,
  }
}
Example #27
Source File: auto.spec.ts    From vue3-treeview with MIT License 4 votes vote down vote up
describe("test auto checkbox", () => {
    let node = null;

    let nodes = null;

    let storeProps = null;

    let mode = null;

    beforeEach(() => {
        node = ref({
            children: ["c1", "c2"],
            state: {
                checked: false,
                indeterminate: false
            }
        });
        nodes = ref({
            c1: {
                text: "c1",
                state: {
                    checked: false,
                    indeterminate: false
                }
            },
            c2: {
                text: "c2", 
                state: {
                    checked: false,
                    indeterminate: false
                }
            }
        });
        storeProps = reactive({
            nodes
        });
        mode = auto(node, nodes);
    });

    it("Expect not to be checked", () => {
        expect(mode.checked.value).toBeFalsy();
    });

    it("Expect not to be indeterminate", () => {
        expect(mode.indeterminate.value).toBeFalsy();
    });

    it("Expect none to be checked", () => {
        expect(mode.noneChecked.value).toBeTruthy();
    });

    it("Expect not all to be checked" , () => {
        expect(mode.allChecked.value).toBeFalsy();
    });

    it("Expect to no have some indeterminate", () => {
        expect(mode.someIndeterminate.value).toBeFalsy();
    });

    it("Expect not some checked" , () => {
        expect(mode.someChecked.value).toBeFalsy();
    });

    it("Expect children to have state", () => {
        expect(nodes.value.c1.state).toBeDefined();
        expect(nodes.value.c2.state).toBeDefined();
    });

    it("Expect to check node", () => {
        mode.click();
        expect(node.value.state.checked).toBeTruthy();
        expect(node.value.state.indeterminate).toBeFalsy();
    });

    it("Expect to rebuild", () => {
        node.value.state.checked = true;
        mode.rebuild();
        expect(nodes.value.c1.state.checked).toBeTruthy();
        expect(nodes.value.c2.state.checked).toBeTruthy();
    });

    it("Expect not to update state", () => {
        node.value.children = [];
        mode.updateState();
        expect(nodes.value.c1.state.checked).toBeFalsy();
        expect(nodes.value.c2.state.checked).toBeFalsy();
    });

    it("Expect to reset node checked on update state", () => {
        node.value.state.checked = true;
        node.value.state.indeterminate = true;
        mode.updateState();
        expect(node.value.state.checked).toBeFalsy();
        expect(node.value.state.indeterminate).toBeFalsy();
    });

    it("Expect node to be indeterminate after update", () => {
        nodes.value.c1.state.indeterminate = true;
        mode.updateState();
        expect(node.value.state.checked).toBeFalsy();
        expect(node.value.state.indeterminate).toBeTruthy();
    });

    it("Expect to update nothing", () => {
        node.value.children = null;
        mode.updateState();
        expect(node.value.state.checked).toBeFalsy();
        expect(node.value.state.indeterminate).toBeFalsy();
    });
});
Example #28
Source File: useCommon.spec.ts    From vue3-treeview with MIT License 4 votes vote down vote up
describe("test useCommon", () => {
    let props = null;

    let useTest = null;

    const config = {
        roots: ["id1"],
        disabled: false,
        editing: null
    };

    const storeProps = reactive({
        nodes: {},
        config
    });

    const v = require("vue");

    let state = null;

    v.inject = jest.fn((s) => {
        return s === "emitter" ? jest.fn() : {
            config: ref(config)
        }
    });

    beforeEach(() => {
        props = reactive({
            node: ref({
                id: "test"
            })
        });
        const id = createState(storeProps);
        state = states.get(id);
        useTest = useCommon(props);
    });

    it("Expect to have state", () => {
        expect(props.node.state).toBeDefined();
    });
    
    it("Expect to have node", () => {
        expect(useTest.hasNode.value).toBeTruthy();
    });

    it("Expect to have config", () => {
        expect(useTest.hasConfig.value).toBeTruthy();
    });

    it("Expect to have state", () => {
        expect(useTest.hasState.value).toBeTruthy();
    });

    it("Expect not to be disabled", () => {
        expect(useTest.disabled.value).toBeFalsy();
    });

    it("Expect not to be editable", () => {
        expect(useTest.editable.value).toBeFalsy();
    });

    it("Expect not to be edited", () => {
        expect(useTest.editing.value).toBeFalsy();
    });

    it("Expect to be editable", () => {
        state.config.value.editable = true;
        props.node.state.editable = true;
        expect(useTest.editable.value).toBeTruthy();
    });

    it("Expect to be editing", () => {
        state.config.value.editable = true;
        state.config.value.editing = "test";
        props.node.state.editable = true;
        expect(useTest.editable.value).toBeTruthy();
    });

    it("Expect to blur", () => {
        state.config.value.editing = "tata";
        const e = {
            currentTarget: document.createElement("div"),
            relatedTarget: document.createElement("div"),
            type: "blur"
        };
        useTest.blur(e);
        expect(state.config.value.editing).toBeNull();
    });
});
Example #29
Source File: useDragAndDrop.spec.ts    From vue3-treeview with MIT License 4 votes vote down vote up
describe("test use Drag and Drop", () => {
    let fakeCmn = null;

    let useTest = null;

    let wrapper = null;

    let props = null;

    let node = null;

    let nodes = null;

    let node2 = null;

    let c1 = null;

    let c2 = null;

    let c3 = null;

    let config = null;

    let storeProps = null;

    let fakeDragged = null;

    let fakeTarget = null;

    let fakeContext = null;

    let state = null;

    beforeEach(() => {
        node = {
            text: "id1",
            id: "id1",
            children: ["id11", "id12"],
            state: {}
        };
        node2 = {
            text: "id2",
            id: "id2",
            children: ["id21"],
            state:{}
        };
        c1 = {
            id: "id11",
            text: "id11",
            parent: "id1",
            state: {}
        };
        c2 = {
            id: "id12",
            text: "id12",
            parent: "id1",
            state: {}
        };
        c3 = {
            id: "id21",
            text: "id21",
            parent: "id2",
            state: {}
        };
        config = ref({
            roots: ["id1", "id2"]
        });
        nodes = {
            id1: node,
            id11: c1,
            id12: c2,
            id2: node2,
            id21: c3
        };
        storeProps = reactive({
            nodes,
            config
        });
        const id = createState(storeProps);
        state = states.get(id);
        wrapper = ref(document.createElement("div"));
        fakeCmn = {
            node: ref(node),
            config,
            wrapper,
            disabled: ref(false),
            root: {
                emit: jest.fn()
            },
            state
        };
        fakeDragged = {
            element: null,
            node: null,
            parentId: null,
            wrapper: null
        };
        fakeTarget = {
            element: null,
            node: 'id1',
            parentId: null,
            wrapper: wrapper.value
        };
        fakeContext = {
            dataTransfer: null,
            dragged: fakeDragged,
            target: fakeTarget,
            evt: undefined,
            external: false
        };        
        props = {
            parentId: ref(null)
        };
        useTest = useDragAndDrop(fakeCmn, props);
    });

    it("Expect not to be draggable", () => {
        expect(useTest.draggable.value).toBeFalsy();
    });

    it("Expect not to be droppable", () => {
        expect(useTest.droppable.value).toBeFalsy();
    });

    it("Expect element to be null", () => {
        expect(useTest.element.value).toBeNull();
    });

    it("Expect to have have basic class", () => {
        expect(useTest.dragClass.value).toMatchObject([
            null,
            null,
            null,
            null,
            null,
        ]);
    });

    it("Expect to start drag", () => {
        config.value.dragAndDrop = ref(true);
        const spy = jest.spyOn(fakeCmn.root, "emit");
        fakeCmn.node.value.state.draggable = true;
        useTest.dragstart();
        fakeDragged.node = node;
        fakeDragged.wrapper = wrapper.value;
        expect(state.dragged.value).toMatchObject(fakeDragged);
        expect(spy).toBeCalledWith(dragEvents.start, fakeContext);
    });

    it("Expect to emit event on drag end when nothing started", () => {
        config.value.dragAndDrop = ref(true);
        const spy = jest.spyOn(fakeCmn.root, "emit");
        useTest.dragend();
        expect(spy).toBeCalledWith(dragEvents.end, fakeContext);
    });

    it("Expect to emit on drag enter", () => {
        const spy = jest.spyOn(fakeCmn.root, "emit");
        useTest.dragenter();
        expect(spy).toBeCalledWith(dragEvents.enter, fakeContext);
    });

    it("Expect to emit on drag leave", () => {
        const spy = jest.spyOn(fakeCmn.root, "emit");
        useTest.dragleave();
        expect(spy).toBeCalledWith(dragEvents.leave, fakeContext);
    });

    it("Expect to be same node", () => {
        state.dragged.value = fakeDragged;
        fakeDragged.node = node;
        useTest.dragover();
    });

    it("Expect to drag node 2 in node 1", () => {
        node2.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.node = node2;
        wrapper.value.getBoundingClientRect = jest.fn(() => {
            return {
                bottom: 0, 
                height: 40, 
                left: 0, 
                right: 0, 
                top: 0, 
                width: 0
            };
        });
        useTest.dragover({ pageY: 20 });
        expect(useTest.pos.value).toBe(DragPosition.in); 
    });

    it("Expect to drag node 2 over node 1", () => {
        node2.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.node = node2;
        wrapper.value.getBoundingClientRect = jest.fn(() => {
            return {
                bottom: 0, 
                height: 40, 
                left: 0, 
                right: 0, 
                top: 0, 
                width: 0
            };
        });
        useTest.dragover({ pageY: 1 });
        expect(useTest.pos.value).toBe(DragPosition.over); 
    });

    it("Expect to drag child node 3 under node 1", () => {
        c3.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.node = c3;
        wrapper.value.getBoundingClientRect = jest.fn(() => {
            return {
                bottom: 0, 
                height: 40, 
                left: 0, 
                right: 0, 
                top: 0, 
                width: 0
            };
        });
        useTest.dragover({ pageY: 35 });
        expect(useTest.pos.value).toBe(DragPosition.under);
    });

    it("Expect to insert child node 2 over node 1", () => {
        c3.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.parentId = "id2";
        fakeDragged.node = c3;
        props.parentId.value = null;
        useTest.pos.value = DragPosition.over;
        useTest.drop();
        expect(config.value.roots).toMatchObject([
            "id21", "id1", "id2"
        ])
        expect(node2.children).toMatchObject([]);
    });

    it("Expect to insert child node 2 under node 1", () => {
        c3.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.parentId = "id2";
        fakeDragged.node = c3;
        props.parentId.value = null;
        useTest.pos.value = DragPosition.under;
        useTest.drop();
        expect(config.value.roots).toMatchObject([
            "id1", "id21", "id2"
        ])
        expect(node2.children).toMatchObject([]);
    });

    it("Expect to insert child node 2 in node 1", () => {
        c3.state.draggable = true;
        state.dragged.value = fakeDragged;
        fakeDragged.parentId = "id2";
        fakeDragged.node = c3;
        config.value.dragAndDrop = true;
        props.parentId.value = null;
        useTest.pos.value = DragPosition.in;
        useTest.drop();
        expect(node.children).toMatchObject([
            "id21", "id11", "id12"
        ])
        expect(node2.children).toMatchObject([]);
    });
});