fs#rmdirSync TypeScript Examples

The following examples show how to use fs#rmdirSync. 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: copy-eslint-config.ts    From s-libs with MIT License 6 votes vote down vote up
export function copyEslintConfig() {
  const targetFolder = 'dist/eslint-config-ng';
  rmdirSync(targetFolder, { recursive: true });
  mkdirSync(targetFolder);

  const srcFolder = 'projects/eslint-config-ng';
  for (const file of readdirSync(srcFolder)) {
    copyFileSync(join(srcFolder, file), join(targetFolder, file));
  }
}
Example #2
Source File: index.ts    From swagger-typescript with MIT License 6 votes vote down vote up
function convertTsToJs(dir: string, files: string[]) {
  build({
    basePath: ".", // always required, used for relative paths
    compilerOptions: {
      listFiles: true,
      outDir: dir,
      declaration: true,
      skipLibCheck: true,
      module: "esnext",
      target: "esnext",
      lib: ["esnext"],
    },
    files: files.map((file) => `${dir}/${file}.ts`),
  });

  files.forEach((file) => {
    if (existsSync(`${dir}/${file}.ts`)) {
      rmdirSync(`${dir}/${file}.ts`, { recursive: true });
    }
  });
}
Example #3
Source File: none.ts    From joi-to-typescript with MIT License 6 votes vote down vote up
describe('no schemas in directory', () => {
  const typeOutputDirectory = './src/__tests__/none/interfaces';

  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('Throw and no index file', async () => {
    expect(async () => {
      await convertFromDirectory({
        schemaDirectory: './src/__tests__/none/schemas',
        typeOutputDirectory
      });
    }).rejects.toThrowError();

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeFalsy();
  });
});
Example #4
Source File: empty.ts    From joi-to-typescript with MIT License 6 votes vote down vote up
describe('empty schema directory', () => {
  const typeOutputDirectory = './src/__tests__/empty/interfaces';

  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('Throw and no index file', async () => {
    expect(async () => {
      await convertFromDirectory({
        schemaDirectory: './src/__tests__/empty/schemas',
        typeOutputDirectory
      });
    }).rejects.toThrowError();

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeFalsy();
  });
});
Example #5
Source File: fromFile.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('Create interfaces from schema files', () => {
  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('does reading form files work', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/fromFile/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);
  });
  test('index.ts file contain correct content', () => {
    const indexContent = readFileSync(`${typeOutputDirectory}/index.ts`).toString();

    expect(indexContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export * from './FooBar';
export * from './One';
`
    );
  });
  test('One.ts file exists and its content', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

/**
 * a test schema definition
 */
export interface TestSchema {
  'yellow.flower'?: string;
  name?: string;
  propertyName1: boolean;
}
`
    );
  });

  test('FooBar.ts file contain correct content', () => {
    const fooBarContent = readFileSync(`${typeOutputDirectory}/FooBar.ts`).toString();

    expect(fooBarContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Bar {
  /**
   * Id
   * @example 1
   */
  id: number;
}

export interface Foo {
  /**
   * Bar
   */
  bar: Bar;
  /**
   * Id
   * @example 1
   */
  id: number;
}
`);
  });
});
Example #6
Source File: file-writer.test.ts    From github-profile-summary-cards with MIT License 5 votes vote down vote up
afterEach(() => {
    rmdirSync(targetFolder, {recursive: true});
});
Example #7
Source File: readme.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('this is the example on the readme', () => {
  const typeOutputDirectory = './src/__tests__/readme/interfaces';
  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('a good example schema', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/readme/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const readmeContent = readFileSync(`${typeOutputDirectory}/Readme.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Job {
  businessName: string;
  jobTitle: string;
}

/**
 * A list of People
 */
export type People = Person[];

export interface Person {
  firstName: string;
  job?: Job;
  /**
   * Last Name
   */
  lastName: string;
  wallet?: Wallet;
}

export interface Wallet {
  /**
   * Number Property
   */
  [x: string]: number;
  eur: number;
  usd: number;
}
`);
  });
});
Example #8
Source File: multipleFiles.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('can files reference interfaces between schema files', () => {
  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('multipleFiles', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/multipleFiles/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

import { Person } from '.';

export interface Item {
  /**
   * Female Zebra
   */
  femaleZebra?: Zebra;
  /**
   * Male Zebra
   */
  maleZebra?: Zebra;
  name: string;
}

/**
 * A list of People
 */
export type People = Person[];

/**
 * a test schema definition
 */
export interface Test {
  name?: string;
  /**
   * A list of People
   */
  people?: People;
  propertyName1: boolean;
}

export interface Zebra {
  name?: string;
}
`
    );
  });
});
Example #9
Source File: ignoreFiles.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('ignore Files', () => {
  const typeOutputDirectory = './src/__tests__/ignoreFiles/interfaces';
  const schemaDirectory = './src/__tests__/ignoreFiles/schemas';

  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('Ignores file names in ignoreList', async () => {
    const ignoreFiles = ['AddressSchema.ts'];
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      ignoreFiles
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/One.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Person.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Address.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/Employee.ts`)).toBeTruthy();
  });

  test('Ignores folder names in ignoreList', async () => {
    const ignoreFiles = ['subDir/'];
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      ignoreFiles
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/One.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/index.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Person.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Address.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/Employee.ts`)).toBeTruthy();
  });

  test('Ignores a file and folder in an ignore list', async () => {
    const consoleSpy = jest.spyOn(console, 'debug');
    const ignoreFiles = ['subDir2/', 'OneSchema.ts'];
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      ignoreFiles,
      debug: true
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/One.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir/index.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Person.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir/Address.ts`)).toBeTruthy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/index.ts`)).toBeFalsy();
    expect(existsSync(`${typeOutputDirectory}/subDir2/Employee.ts`)).toBeFalsy();

    expect(consoleSpy).toHaveBeenCalledWith(expect.stringMatching(/subDir2 because it's in your ignore files list$/));
    expect(consoleSpy).toHaveBeenCalledWith("Skipping OneSchema.ts because it's in your ignore files list");
  });
});
Example #10
Source File: fromFile.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('Create interfaces from schema files edge cases', () => {
  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('input directory that does not exits', async () => {
    await expect(
      convertFromDirectory({
        schemaDirectory: './src/__tests__/doesnotexist',
        typeOutputDirectory
      })
    ).rejects.toThrowError();
  });

  test('create deep output directory that does not exits', async () => {
    const deepDirectory = './src/__tests__/fromFile/interfaces/fake1/fake2';
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/fromFile/schemas',
      typeOutputDirectory: deepDirectory
    });

    expect(result).toBe(true);
    expect(existsSync(deepDirectory)).toBe(true);
  });

  test('debugging on', async () => {
    const consoleSpy = jest.spyOn(console, 'debug');
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/fromFile/schemas',
      typeOutputDirectory,
      debug: true
    });

    expect(result).toBe(true);
    expect(consoleSpy).toHaveBeenCalledWith('FooBarSchema.ts - Processing');
    expect(consoleSpy).toHaveBeenCalledWith('OneSchema.ts - Processing');
    expect(consoleSpy).toHaveBeenCalledWith('notASchema.ts - Skipped - no Joi Schemas found');
  });
});
Example #11
Source File: Options.ts    From tf2autobot with MIT License 5 votes vote down vote up
function cleanPath(p: string): void {
    if (existsSync(p)) {
        readdirSync(p).map(s => unlinkSync(path.join(p, s)));
        rmdirSync(p);
    }
}
Example #12
Source File: index.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('empty schema directory', () => {
  const typeOutputDirectory = './src/__tests__/files/interfaces';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('does reading form files work', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/files/schemas',
      typeOutputDirectory,
      inputFileFilter: InputFileFilter.ExcludeIndex,
      debug: true
    });

    expect(result).toBe(true);

    const baseInterfaceDir = './src/__tests__/files/interfaces/';
    expect(existsSync(`${baseInterfaceDir}One.ts`)).toBe(true);
    expect(existsSync(`${baseInterfaceDir}React.tsx`)).toBe(false);
  });

  test.concurrent.each([
    'index.ts',
    'HelloSchema.ts',
    '/red/HelloSchema.ts',
    '/HelloSchema.ts',
    '/red/blue/BlackSchema.ts'
  ])('Default Regular Expression Valid: %s', async item => {
    expect(InputFileFilter.Default.test(item)).toBeTruthy();
  });

  test.concurrent.each(['index.tsx', 'index.t', 'frank', 'readme.md', 'foo.java', 'bar.js', '/bar.js'])(
    'Default Regular Expression invalid: %s',
    async item => {
      expect(InputFileFilter.Default.test(item)).toBeFalsy();
    }
  );

  test.concurrent.each(['HelloSchema.ts', '/red/HelloSchema.ts', '/HelloSchema.ts', '/red/blue/BlackSchema.ts'])(
    'Exclude Index Regular Expression Valid: %s',
    async item => {
      expect(InputFileFilter.ExcludeIndex.test(item)).toBeTruthy();
    }
  );

  test.concurrent.each(['index.tsx', 'index.ts', 'index.t', 'frank', 'readme.md', 'foo.java', 'bar.js', '/bar.js'])(
    'Exclude Indext Regular Expression Invalid: %s',
    async item => {
      expect(InputFileFilter.ExcludeIndex.test(item)).toBeFalsy();
    }
  );

  test.concurrent.each([
    'index.ts',
    'HelloSchema.ts',
    '/red/HelloSchema.ts',
    '/HelloSchema.ts',
    '/red/blue/BlackSchema.ts',
    'index.js',
    'HelloSchema.js',
    '/red/HelloSchema.js',
    '/HelloSchema.js',
    '/red/blue/BlackSchema.js'
  ])('Include JavaScript Regular Expression Valid: %s', async item => {
    expect(InputFileFilter.IncludeJavaScript.test(item)).toBeTruthy();
  });

  test.concurrent.each(['index.tsx', 'index.jsx', 'index.t', 'frank', 'readme.md', 'foo.java', 'bar.cjs', '/bar.jsm'])(
    'Include JavaScript Regular Expression Invalid: %s',
    async item => {
      expect(InputFileFilter.IncludeJavaScript.test(item)).toBeFalsy();
    }
  );
});
Example #13
Source File: index.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('test the use of joi.concat()', () => {
  const typeOutputDirectory = './src/__tests__/concat/interfaces';
  const schemaDirectory = './src/__tests__/concat/schemas';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('generate className interfaces', async () => {
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      debug: true
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeTruthy();
  });

  // it would be nice to auto remove this schema suffix but that could break the Joi, the safest is to warn the user about
  // how they could do it better
  test('no className with schema as suffix', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/FooBar.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Bar {
  b?: string;
}

export interface Foo {
  a?: string;
}

export interface FooBar {
  a?: string;
  b?: string;
}
`
    );
  });
});
Example #14
Source File: cast.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('Cast primitive types', () => {
  const typeOutputDirectory = './src/__tests__/cast/interfaces';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('casts all variations from file', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/cast/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type NumberType = number;

export interface Numbers {
  bool: number;
  day: number;
}

export type StringType = string;

export interface Strings {
  num: string;
}
`
    );
  });
});
Example #15
Source File: array.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('Array types', () => {
  const typeOutputDirectory = './src/__tests__/array/interfaces';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('array variations from file', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/array/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Item {
  name: string;
}

/**
 * a test schema definition
 */
export interface Test {
  items?: Item[];
  name?: string;
  propertyName1: boolean;
}

/**
 * A list of Test object
 */
export type TestList = Test[];
`
    );
  });

  test('test to ensure second items() is ignored', () => {
    // this tests this code
    //const childrenContent = children.map(child => typeContentToTsHelper(settings, child, indentLevel));
    //if (childrenContent.length > 1) {
    //  /* istanbul ignore next */
    //  throw new Error('Multiple array item types not supported');
    //}
    const schema = Joi.array()
      .items(Joi.string().description('one'))
      .items(Joi.number().description('two'))
      .required()
      .meta({ className: 'TestList' })
      .description('A list of Test object');

    const result = convertSchema({ sortPropertiesByName: true }, schema);
    expect(result).not.toBeUndefined;
    expect(result?.content).toBe(`/**
 * A list of Test object
 */
export type TestList = string[];`);
  });
});
Example #16
Source File: alternatives.ts    From joi-to-typescript with MIT License 5 votes vote down vote up
describe('alternative types', () => {
  const typeOutputDirectory = './src/__tests__/alternatives/interfaces';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('vaiations of alternatives from file', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/alternatives/schemas',
      typeOutputDirectory,
      sortPropertiesByName: false
    });

    expect(result).toBe(true);

    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();
    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

/**
 * a description for basic
 */
export type Basic = number | string;

export interface Other {
  other?: string;
}

/**
 * a test schema definition
 */
export interface Test {
  name?: string;
  value?: Thing | Other;
  /**
   * a description for basic
   */
  basic?: Basic;
}

/**
 * A list of Test object
 */
export type TestList = (boolean | string)[];

export interface Thing {
  thing: string;
}
`
    );
  });

  test('blank alternative throws in joi', () => {
    expect(() => {
      Joi.alternatives().try().meta({ className: 'Basic' }).description('a description for basic');
    }).toThrow();
  });

  test.skip('blank alternative thrown by joi but extra test if joi changes it', () => {
    expect(() => {
      const invalidSchema = Joi.alternatives()
        .try()
        .meta({ className: 'Basic' })
        .description('a description for basic');

      // the next code will not run as already thrown
      convertSchema({}, invalidSchema);
    }).toThrow();
  });
});
Example #17
Source File: index.ts    From airnode with MIT License 5 votes vote down vote up
wipe = () => {
  rmdirSync(CACHE_BASE_PATH, { recursive: true });
}
Example #18
Source File: permission-request.ts    From beacon-sdk with MIT License 5 votes vote down vote up
rune2e = async () => {
  try {
    rmdirSync('./e2e/output', { recursive: true })
  } catch {}
  mkdirSync('./e2e/output/dapp', { recursive: true })
  mkdirSync('./e2e/output/wallet', { recursive: true })
  const browser = await puppeteer.launch()
  const context = browser.defaultBrowserContext()
  context.overridePermissions('http://localhost:8081', ['clipboard-read', 'clipboard-write'])

  console.log('started')

  const pages: BeaconPages = {
    dapp: await createPage(browser, dAppURL),
    wallet: await createPage(browser, walletURL)
  }

  const ssDapp = new PuppeteerMassScreenshots()
  const ssWallet = new PuppeteerMassScreenshots()

  await ssDapp.init(pages.dapp, './e2e/output/dapp')
  await ssWallet.init(pages.wallet, './e2e/output/wallet')

  console.log('pages-created')

  await ssDapp.start()
  await ssWallet.start()
  await takeScreenshots(pages, '1-init')

  await pages.dapp.click('#requestPermission')
  await sleep(1000)
  await takeScreenshots(pages, '2-popup')

  const newPagePromise = new Promise<puppeteer.Page>((x) =>
    browser.once('targetcreated', (target) => x(target.page()))
  ) // declare promise
  const desktopWallet = (
    await pages.dapp.evaluateHandle(
      `document.querySelector("[id^='beacon-alert-wrapper']").shadowRoot.querySelector("#wallet_kukai_web")`
    )
  ).asElement()
  if (desktopWallet) {
    await desktopWallet.click()
  }
  const pairingPage = await newPagePromise
  const pairingUrl = pairingPage.url()
  console.log('pairingUrl', pairingUrl)
  await pairingPage.close()

  await sleep(1000)

  await pages.wallet.type(
    '#hidden-input',
    pairingUrl.slice('https://wallet.kukai.app/?type=tzip10&data='.length)
  )

  await pages.wallet.click('#paste')
  await sleep(5000)

  await takeScreenshots(pages, '3-connected')
  //   await pages.dapp.waitForSelector('title')
  //   const connectButtonSelector =
  //     '#root > div > div.page-container.header-top-container > div > div.text-center.hidden.lg\\:flex.main-menu-container > div:nth-child(5) > button'

  //   await pageDapp.waitForSelector(connectButtonSelector)
  //   await pageDapp.click(connectButtonSelector)

  await ssDapp.stop()
  await ssWallet.stop()
  await browser.close()
  server1.stop()
  server2.stop()
}
Example #19
Source File: className.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('test the use of .meta({className: ""})', () => {
  const typeOutputDirectory = './src/__tests__/className/interfaces';
  const schemaDirectory = './src/__tests__/className/schemas';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('generate className interfaces', async () => {
    const consoleSpy = jest.spyOn(console, 'debug');
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      debug: true
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeTruthy();

    expect(consoleSpy).toHaveBeenCalledWith(
      "It is recommended you update the Joi Schema 'noClassNameSchema' similar to: noClassNameSchema = Joi.object().meta({className:'noClassName'})"
    );
  });

  test('no className', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/NoClassNameTest.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface noClassNametest {
  name?: string;
}
`
    );
  });

  // it would be nice to auto remove this schema suffix but that could break the Joi, the safest is to warn the user about
  // how they could do it better
  test('no className with schema as suffix', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/NoClassName.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface noClassNameSchema {
  name?: string;
}
`
    );
  });

  test('className', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/ClassName.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Frank {
  name?: string;
}
`
    );
  });

  test('className property names', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/ClassNameProperty.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type Name = string;

export interface className {
  name?: Name;
}
`
    );
  });

  test('className property names with spaces', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/ClassNamePropertySpaced.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type CustomerPhoneNumber = string;

export type EmailAddress = string;

export type Name = string;

export interface spacedClassName {
  email?: EmailAddress;
  name?: Name;
  phone?: CustomerPhoneNumber;
}
`
    );
  });

  test('no meta({className:""}) and no property name', () => {
    expect(() => {
      convertSchema(
        {},
        Joi.object({
          name: Joi.string().optional()
        })
      );
    }).toThrowError();
  });

  test('no meta({}) and no property name', () => {
    const details: Describe = { type: 'string', metas: [{ something: '' } as unknown] } as Describe;
    ensureInterfaceorTypeName({} as Settings, details, 'Force Me');
    expect(details.metas).toMatchObject([{ something: '' }, { className: 'Force Me' }]);
  });
});
Example #20
Source File: indent.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('indentation', () => {
  const typeOutputDirectory = './src/__tests__/indentation/interfaces';
  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('default indentation', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/indentation/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const content = readFileSync(`${typeOutputDirectory}/Nested.ts`).toString();

    expect(content).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Nested {
  address?: {
    line1: string;
    line2?: string;
    suburb: string;
  };
  connections?: {
    frogs?: {
      colour: string;
      legs?: {
        toe: number;
      };
    }[];
    name?: string;
    type?: string[];
  }[];
  name?: string;
}
`);
  });

  test('4 space indentation', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/indentation/schemas',
      typeOutputDirectory,
      indentationChacters: '    '
    });

    expect(result).toBe(true);

    const content = readFileSync(`${typeOutputDirectory}/Nested.ts`).toString();

    expect(content).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Nested {
    address?: {
        line1: string;
        line2?: string;
        suburb: string;
    };
    connections?: {
        frogs?: {
            colour: string;
            legs?: {
                toe: number;
            };
        }[];
        name?: string;
        type?: string[];
    }[];
    name?: string;
}
`);
  });

  test('tab indentation', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/indentation/schemas',
      typeOutputDirectory,
      indentationChacters: '\t'
    });

    expect(result).toBe(true);

    const content = readFileSync(`${typeOutputDirectory}/Nested.ts`).toString();

    // Had to use \t as my editor is setup to convert tabs to spaces
    expect(content).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Nested {
\taddress?: {
\t\tline1: string;
\t\tline2?: string;
\t\tsuburb: string;
\t};
\tconnections?: {
\t\tfrogs?: {
\t\t\tcolour: string;
\t\t\tlegs?: {
\t\t\t\ttoe: number;
\t\t\t};
\t\t}[];
\t\tname?: string;
\t\ttype?: string[];
\t}[];
\tname?: string;
}
`);
  });
});
Example #21
Source File: label.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('test the use of .label()', () => {
  const typeOutputDirectory = './src/__tests__/label/interfaces';
  const schemaDirectory = './src/__tests__/label/schemas';

  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('generate label interfaces', async () => {
    const consoleSpy = jest.spyOn(console, 'debug');
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      debug: true,
      useLabelAsInterfaceName: true
    });

    expect(result).toBe(true);

    expect(existsSync(`${typeOutputDirectory}/index.ts`)).toBeTruthy();

    expect(consoleSpy).toHaveBeenCalledWith(
      "It is recommended you update the Joi Schema 'nolabelSchema' similar to: nolabelSchema = Joi.object().label('nolabel')"
    );
  });

  test('no label', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/NoLabelTest.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface nolabeltest {
  name?: string;
}
`
    );
  });

  // it would be nice to auto remove this schema suffix but that could break the Joi, the safest is to warn the user about
  // how they could do it better
  test('no label with schema as suffix', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/NoLabel.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface nolabelSchema {
  name?: string;
}
`
    );
  });

  test('label', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/Label.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Frank {
  name?: string;
}
`
    );
  });

  test('labeled property names', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/LabelProperty.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type Name = string;

export interface label {
  name?: Name;
}
`
    );
  });

  test('labeled property names with spaces', () => {
    const oneContent = readFileSync(`${typeOutputDirectory}/LabelPropertySpaced.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type CustomerPhoneNumber = string;

export type EmailAddress = string;

export type Name = string;

export interface spacedLabel {
  email?: EmailAddress;
  name?: Name;
  phone?: CustomerPhoneNumber;
}
`
    );
  });

  test('no label() and no property name', () => {
    expect(() => {
      convertSchema(
        { useLabelAsInterfaceName: true },
        Joi.object({
          name: Joi.string().optional()
        })
      );
    }).toThrowError();
  });

  test('Joi.id() instead of Joi.label()', () => {
    const schema = Joi.object({
      name: Joi.string()
    }).id('Test');
    try {
      convertSchema({ debug: true, useLabelAsInterfaceName: true }, schema);
      expect(true).toBe(false);
    } catch (error) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      expect(error && (error as any).message).toBe(
        'At least one "object" does not have .label(\'\'). Details: {"type":"object","flags":{"id":"Test"},"keys":{"name":{"type":"string"}}}'
      );
    }
  });
});
Example #22
Source File: index.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('Primitive Types', () => {
  const typeOutputDirectory = './src/__tests__/primitiveTypes/interfaces';
  beforeAll(async () => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/primitiveTypes/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);
  });

  test('String base schema', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Email.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface CompanySchema {
  email?: Email;
}

export type Email = string;

export interface UserSchema {
  email: Email;
}
`);
  });

  test('number base schema', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Counter.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface CompanySchema {
  counter?: Counter;
}

export type Counter = number;

export interface UserSchema {
  counter: Counter;
}
`);
  });

  test('Date base schema', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/DateField.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface CompanySchema {
  counter?: DateField;
}

export type DateField = Date;

export interface UserSchema {
  counter: DateField;
}
`);
  });

  test('boolean base schema', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Boolean.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type Boolean = boolean;

export interface CompanySchema {
  counter?: Boolean;
}

export interface UserSchema {
  counter: Boolean;
}
`);
  });

  test('allow on base schema', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Allow.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export type Blank = string;

export type BlankNull = string | null | '';

/**
 * This is date
 */
export type DateOptions = Date | null;

/**
 * Test Schema Name
 */
export type Name = string;

export type NormalList = 'red' | 'green' | 'blue';

export type NormalRequiredList = 'red' | 'green' | 'blue';

/**
 * nullable
 */
export type NullName = string | null;

export type NullNumber = number | null;

export type Numbers = 1 | 2 | 3 | 4 | 5;
`);
  });

  test('ensure primitive types are exported/imported correctly', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Using.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface UsingOtherTypesSchema {
  property?: string | null | '';
}
`);
  });

  test('union/alternative primitive types', async () => {
    const readmeContent = readFileSync(`${typeOutputDirectory}/Union.ts`).toString();

    expect(readmeContent).toBe(`/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface CompanySchema {
  counter?: Union;
}

export type Union = string | number;

export interface UserSchema {
  counter: Union;
}
`);
  });
});
Example #23
Source File: allow.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('union types using allow()', () => {
  beforeAll(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('many variations of `allow()`', () => {
    // allowing an empty string is still just a string
    const schema = Joi.object({
      name: Joi.string().optional().description('Test Schema Name').allow(''),
      nullName: Joi.string().optional().description('nullable').allow(null),
      blankNull: Joi.string().optional().allow(null, ''),
      blank: Joi.string().allow(''),
      normalList: Joi.string().allow('red', 'green', 'blue'),
      normalRequiredList: Joi.string().allow('red', 'green', 'blue').required(),
      numbers: Joi.number().optional().allow(1, 2, 3, 4, 5),
      nullNumber: Joi.number().optional().allow(null),
      date: Joi.date().allow(null).description('This is date')
    })
      .meta({ className: 'TestSchema' })
      .description('a test schema definition');

    const result = convertSchema({ sortPropertiesByName: false }, schema);
    expect(result).not.toBeUndefined;
    expect(result?.content).toBe(`/**
 * a test schema definition
 */
export interface TestSchema {
  /**
   * Test Schema Name
   */
  name?: string;
  /**
   * nullable
   */
  nullName?: string | null;
  blankNull?: string | null | '';
  blank?: string;
  normalList?: 'red' | 'green' | 'blue';
  normalRequiredList: 'red' | 'green' | 'blue';
  numbers?: 1 | 2 | 3 | 4 | 5;
  nullNumber?: number | null;
  /**
   * This is date
   */
  date?: Date | null;
}`);
  });

  test('test an invalid variation of `allow()`', () => {
    expect(() => {
      const invalidSchema = Joi.object({
        blankNullUndefined: Joi.string().optional().allow(null, '', undefined),
        blankNullUndefinedRequired: Joi.string().required().allow(null, '', undefined)
      })
        .meta({ className: 'TestSchema' })
        .description('a test schema definition');

      const invalidResult = convertSchema({}, invalidSchema);
      // eslint-disable-next-line no-console
      console.log(invalidResult);
    }).toThrow();
  });

  test('null `allow()`', () => {
    const schema = Joi.object({
      obj: Joi.object().allow(null),
      arr: Joi.array().items(Joi.string()).allow(null),
      // then some tests for things you can do but probably shouldnt
      sillyProperty: Joi.object().allow(null, 'joe'),
      sillyArray: Joi.array().items(Joi.string()).allow(null, 'fred')
    }).meta({ className: 'TestSchema' });

    const result = convertSchema({ sortPropertiesByName: false }, schema);
    expect(result).not.toBeUndefined;
    expect(result?.content).toBe(`export interface TestSchema {
  obj?: object | null;
  arr?: string[] | null;
  sillyProperty?: object | null | 'joe';
  sillyArray?: string[] | null | 'fred';
}`);
  });

  test('object allow null on complex type', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: './src/__tests__/allow/schemas',
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const oneContent = readFileSync(`${typeOutputDirectory}/Parent.ts`).toString();

    expect(oneContent).toBe(
      `/**
 * This file was automatically generated by joi-to-typescript
 * Do not modify this file manually
 */

export interface Child {
  item: number;
}

export interface Parent {
  child: Child | null;
}
`
    );
  });

  test('Enum `allow()`', () => {
    enum Test {
      Option1 = 0,
      Option2 = 1,
      Option3 = 2
    }

    const schema = Joi.object({
      enumeration: Joi.allow(...Object.values(Test))
    }).meta({ className: 'TestSchema' });

    const result = convertSchema({ sortPropertiesByName: false }, schema);
    expect(result).not.toBeUndefined;
    expect(result?.content).toBe(`export interface TestSchema {
  enumeration?: 'Option1' | 'Option2' | 'Option3' | 0 | 1 | 2;
}`);
  });


  test('allow joi.ref dont crash', () => {
    const schema = Joi.object()
  .label('SignUp')
  .keys({
    password: Joi.string()
      .required()
      .description('The password of the authenticating user')
      .example('test-PASSWORD123'),
    repeatPassword: Joi.string()
      .required()
      .allow(Joi.ref('password'))
      .description('Repeat the password to ensure no typos')
      .example('test-PASSWORD123')
  }).meta({ className: 'TestSchema' });

  const result = convertSchema({}, schema);
  expect(result).not.toBeUndefined;
  expect(result?.content).toBe(`export interface TestSchema {
  /**
   * The password of the authenticating user
   * @example test-PASSWORD123
   */
  password: string;
  /**
   * Repeat the password to ensure no typos
   * @example test-PASSWORD123
   */
  repeatPassword: string;
}`);
  })
});
Example #24
Source File: subDirectories.ts    From joi-to-typescript with MIT License 4 votes vote down vote up
describe('subDirectories', () => {
  const typeOutputDirectory = './src/__tests__/subDirectories/interfaces';
  const schemaDirectory = './src/__tests__/subDirectories/schemas';

  beforeEach(() => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
  });

  test('Sub-Directory - Defaults [Tree]', async () => {
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory
    });

    expect(result).toBe(true);

    const rootIndexContent = readFileSync(`${typeOutputDirectory}/index.ts`).toString();
    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();
    const subDirIndexContent = readFileSync(`${typeOutputDirectory}/subDir/index.ts`).toString();
    const personContent = readFileSync(`${typeOutputDirectory}/subDir/Person.ts`).toString();
    const addressContent = readFileSync(`${typeOutputDirectory}/subDir/Address.ts`).toString();
    const subDir2IndexContent = readFileSync(`${typeOutputDirectory}/subDir2/index.ts`).toString();
    const employeeContent = readFileSync(`${typeOutputDirectory}/subDir2/Employee.ts`).toString();

    expect(rootIndexContent).toBe(AssertionCriteria.defaultRootIndexContent);
    expect(oneContent).toBe(AssertionCriteria.oneContentTree);
    expect(subDirIndexContent).toBe(AssertionCriteria.subDirIndexContent);
    expect(personContent).toBe(AssertionCriteria.personContentTree);
    expect(addressContent).toBe(AssertionCriteria.addressContent);
    expect(subDir2IndexContent).toBe(AssertionCriteria.subDir2IndexContent);
    expect(employeeContent).toBe(AssertionCriteria.employeeContentTree);
  });

  test('Sub-Directory - Flatten', async () => {
    if (existsSync(typeOutputDirectory)) {
      rmdirSync(typeOutputDirectory, { recursive: true });
    }
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      flattenTree: true
    });

    expect(result).toBe(true);

    const rootIndexContent = readFileSync(`${typeOutputDirectory}/index.ts`).toString();
    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();
    const personContent = readFileSync(`${typeOutputDirectory}/Person.ts`).toString();
    const addressContent = readFileSync(`${typeOutputDirectory}/Address.ts`).toString();
    const employeeContent = readFileSync(`${typeOutputDirectory}/Employee.ts`).toString();

    expect(rootIndexContent).toBe(AssertionCriteria.flattenedRootIndexContent);
    expect(oneContent).toBe(AssertionCriteria.oneContentFlatOrIndexAll);
    expect(personContent).toBe(AssertionCriteria.personContentTree);
    expect(addressContent).toBe(AssertionCriteria.addressContent);
    expect(employeeContent).toBe(AssertionCriteria.employeeContentFlattened);
  });

  test('Sub-Directory - Tree Index All to Root', async () => {
    const result = await convertFromDirectory({
      schemaDirectory,
      typeOutputDirectory,
      indexAllToRoot: true
    });

    expect(result).toBe(true);

    const rootIndexContent = readFileSync(`${typeOutputDirectory}/index.ts`).toString();
    const oneContent = readFileSync(`${typeOutputDirectory}/One.ts`).toString();
    const personContent = readFileSync(`${typeOutputDirectory}/subDir/Person.ts`).toString();
    const addressContent = readFileSync(`${typeOutputDirectory}/subDir/Address.ts`).toString();
    const employeeContent = readFileSync(`${typeOutputDirectory}/subDir2/Employee.ts`).toString();

    expect(rootIndexContent).toBe(AssertionCriteria.indexAllToRootIndexContent);
    expect(oneContent).toBe(AssertionCriteria.oneContentFlatOrIndexAll);
    expect(personContent).toBe(AssertionCriteria.personRootIndexContent);
    expect(addressContent).toBe(AssertionCriteria.addressContent);
    expect(employeeContent).toBe(AssertionCriteria.employeeRootIndexContent);
  });

  test('Sub-Directory - Root Directory Only', async () => {
    const result = await convertFromDirectory({
      schemaDirectory: schemaDirectory + '/subDir', // Need to choose a directory with schemas that don't contain outer/sub dependencies.
      typeOutputDirectory,
      rootDirectoryOnly: true
    });

    expect(result).toBe(true);

    const rootIndexContent = readFileSync(`${typeOutputDirectory}/index.ts`).toString();
    const addressContent = readFileSync(`${typeOutputDirectory}/Address.ts`).toString();

    expect(rootIndexContent).toBe(AssertionCriteria.subDirIndexContent);
    expect(addressContent).toBe(AssertionCriteria.addressContent);
  });
});
Example #25
Source File: CompletionProvider.test.ts    From language-tools with MIT License 4 votes vote down vote up
function test(useNewTransformation: boolean) {
    return () => {
        function setup(filename: string) {
            const docManager = new DocumentManager(
                (textDocument) => new Document(textDocument.uri, textDocument.text)
            );
            const lsConfigManager = new LSConfigManager();
            lsConfigManager.update({ svelte: { useNewTransformation } });
            const lsAndTsDocResolver = new LSAndTSDocResolver(
                docManager,
                [pathToUrl(testDir)],
                lsConfigManager
            );
            const completionProvider = new CompletionsProviderImpl(
                lsAndTsDocResolver,
                lsConfigManager
            );
            const filePath = join(testFilesDir, filename);
            const document = docManager.openDocument(<any>{
                uri: pathToUrl(filePath),
                text: ts.sys.readFile(filePath) || ''
            });
            return { completionProvider, document, docManager };
        }

        it('provides completions', async () => {
            const { completionProvider, document } = setup('completions.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(0, 49),
                {
                    triggerKind: CompletionTriggerKind.TriggerCharacter,
                    triggerCharacter: '.'
                }
            );

            assert.ok(
                Array.isArray(completions && completions.items),
                'Expected completion items to be an array'
            );
            assert.ok(completions!.items.length > 0, 'Expected completions to have length');

            const first = completions!.items[0];
            delete first.data;

            assert.deepStrictEqual(first, <CompletionItem>{
                label: 'b',
                insertText: undefined,
                kind: CompletionItemKind.Method,
                sortText: '11',
                commitCharacters: ['.', ',', '('],
                preselect: undefined,
                textEdit: undefined
            });
        });

        it('provides completions on simple property access in mustache', async () => {
            const { completionProvider, document } = setup('mustache.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 3),
                {
                    triggerKind: CompletionTriggerKind.TriggerCharacter,
                    triggerCharacter: '.'
                }
            );

            const first = completions!.items[0];
            delete first.data;

            assert.deepStrictEqual(first, <CompletionItem>{
                label: 'b',
                insertText: undefined,
                kind: CompletionItemKind.Field,
                sortText: '11',
                commitCharacters: ['.', ',', '('],
                preselect: undefined,
                textEdit: undefined
            });
        });

        const editingTestPositionsNewOnly: Array<[number, number, string]> = [
            [21, 22, '@const'],
            [24, 19, 'action directive'],
            [26, 24, 'transition directive'],
            [38, 24, 'animate']
        ];

        const editingTestPositions: Array<[number, number, string]> = [
            [4, 3, 'mustache'],
            [6, 10, '#await'],
            [10, 8, '#key'],
            [14, 9, '@html'],
            [16, 7, '#if'],
            [28, 26, 'element event handler'],
            [30, 21, 'binding'],
            [32, 16, 'element props'],
            [34, 21, 'class directive'],
            [36, 23, 'style directive'],
            [40, 17, 'component props'],
            [42, 22, 'component binding'],
            [44, 29, 'component event handler'],
            ...(useNewTransformation ? editingTestPositionsNewOnly : [])
        ];

        async function testEditingCompletion(position: Position) {
            const { completionProvider, document } = setup('editingCompletion.svelte');

            const completions = await completionProvider.getCompletions(document, position, {
                triggerKind: CompletionTriggerKind.TriggerCharacter,
                triggerCharacter: '.'
            });

            assert.ok(
                completions?.items?.find(
                    (item) => item.label === 'c' && item.kind === CompletionItemKind.Field
                )
            );
        }

        for (const [line, character, type] of editingTestPositions) {
            it(`provides completions on simple property access in ${type}`, async () => {
                await testEditingCompletion({ line, character });
            });
        }

        it('provides event completions', async () => {
            const { completionProvider, document } = setup('component-events-completion.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 5),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            assert.ok(
                Array.isArray(completions && completions.items),
                'Expected completion items to be an array'
            );
            assert.ok(completions!.items.length > 0, 'Expected completions to have length');

            const eventCompletions = completions!.items.filter((item) =>
                item.label.startsWith('on:')
            );

            assert.deepStrictEqual(eventCompletions, <CompletionItem[]>[
                {
                    detail: 'aa: CustomEvent<boolean>',
                    documentation: '',
                    label: 'on:aa',
                    sortText: '-1',
                    textEdit: undefined
                },
                {
                    detail: 'ab: MouseEvent',
                    documentation: {
                        kind: 'markdown',
                        value: 'TEST'
                    },
                    label: 'on:ab',
                    sortText: '-1',
                    textEdit: undefined
                },
                {
                    detail: 'ac: any',
                    documentation: '',
                    label: 'on:ac',
                    sortText: '-1',
                    textEdit: undefined
                }
            ]);
        });

        it('provides event completions with correct text replacement span', async () => {
            const { completionProvider, document } = setup('component-events-completion.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 11),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            assert.ok(
                Array.isArray(completions && completions.items),
                'Expected completion items to be an array'
            );
            assert.ok(completions!.items.length > 0, 'Expected completions to have length');

            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const eventCompletions = completions!.items.filter((item) =>
                item.label.startsWith('on:')
            );

            assert.deepStrictEqual(eventCompletions, <CompletionItem[]>[
                {
                    detail: 'aa: CustomEvent<boolean>',
                    documentation: '',
                    label: 'on:aa',
                    sortText: '-1',
                    textEdit: {
                        newText: 'on:aa',
                        range: {
                            start: {
                                line: 5,
                                character: 7
                            },
                            end: {
                                line: 5,
                                character: 11
                            }
                        }
                    }
                },
                {
                    detail: 'ab: MouseEvent',
                    documentation: {
                        kind: 'markdown',
                        value: 'TEST'
                    },
                    label: 'on:ab',
                    sortText: '-1',
                    textEdit: {
                        newText: 'on:ab',
                        range: {
                            start: {
                                line: 5,
                                character: 7
                            },
                            end: {
                                line: 5,
                                character: 11
                            }
                        }
                    }
                },
                {
                    detail: 'ac: any',
                    documentation: '',
                    label: 'on:ac',
                    sortText: '-1',
                    textEdit: {
                        newText: 'on:ac',
                        range: {
                            start: {
                                line: 5,
                                character: 7
                            },
                            end: {
                                line: 5,
                                character: 11
                            }
                        }
                    }
                }
            ]);
        });

        it('provides event completions from createEventDispatcher', async () => {
            const { completionProvider, document } = setup('component-events-completion.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(6, 5),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const eventCompletions = completions!.items.filter((item) =>
                item.label.startsWith('on:')
            );

            assert.deepStrictEqual(eventCompletions, <CompletionItem[]>[
                {
                    detail: 'c: CustomEvent<boolean>',
                    documentation: {
                        kind: 'markdown',
                        value: 'abc'
                    },
                    label: 'on:c',
                    sortText: '-1',
                    textEdit: undefined
                }
            ]);
        });

        it('provides event completion for components with type definition', async () => {
            const { completionProvider, document } = setup(
                'component-events-completion-ts-def.svelte'
            );

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(4, 16),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const eventCompletions = completions!.items.filter((item) =>
                item.label.startsWith('on:')
            );

            assert.deepStrictEqual(eventCompletions, <CompletionItem[]>[
                {
                    detail: 'event1: CustomEvent<null>',
                    documentation: '',
                    label: 'on:event1',
                    sortText: '-1',
                    textEdit: {
                        newText: 'on:event1',
                        range: {
                            end: {
                                character: 16,
                                line: 4
                            },
                            start: {
                                character: 14,
                                line: 4
                            }
                        }
                    }
                },
                {
                    detail: 'event2: CustomEvent<string>',
                    documentation: {
                        kind: 'markdown',
                        value: 'documentation for event2'
                    },
                    label: 'on:event2',
                    sortText: '-1',
                    textEdit: {
                        newText: 'on:event2',
                        range: {
                            end: {
                                character: 16,
                                line: 4
                            },
                            start: {
                                character: 14,
                                line: 4
                            }
                        }
                    }
                }
            ]);
        });

        it('provides event completion for components with type definition having multiple declarations of the same event', async () => {
            const { completionProvider, document } = setup(
                'component-events-completion-ts-def.svelte'
            );

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(6, 16),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const eventCompletions = completions!.items.filter((item) =>
                item.label.startsWith('on:')
            );

            assert.deepStrictEqual(eventCompletions, <CompletionItem[]>[
                {
                    detail: 'event1: CustomEvent<string> | CustomEvent<number>',
                    label: 'on:event1',
                    sortText: '-1',
                    documentation: '',
                    textEdit: {
                        newText: 'on:event1',
                        range: {
                            end: {
                                character: 17,
                                line: 6
                            },
                            start: {
                                character: 15,
                                line: 6
                            }
                        }
                    }
                }
            ]);
        });

        it('does not provide completions inside style tag', async () => {
            const { completionProvider, document } = setup('completionsstyle.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(4, 1),
                {
                    triggerKind: CompletionTriggerKind.Invoked,
                    triggerCharacter: 'a'
                }
            );

            assert.ok(completions === null, 'Expected completion to be null');
        });

        it('provides completion resolve info', async () => {
            const filename = 'completions.svelte';
            const { completionProvider, document } = setup(filename);

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(0, 49),
                {
                    triggerKind: CompletionTriggerKind.TriggerCharacter,
                    triggerCharacter: '.'
                }
            );

            const { data } = completions!.items[0];

            assert.deepStrictEqual(data, {
                data: undefined,
                hasAction: undefined,
                insertText: undefined,
                isPackageJsonImport: undefined,
                isImportStatementCompletion: undefined,
                isRecommended: undefined,
                isSnippet: undefined,
                kind: 'method',
                kindModifiers: '',
                labelDetails: undefined,
                name: 'b',
                position: {
                    character: 49,
                    line: 0
                },
                replacementSpan: undefined,
                sortText: '11',
                source: undefined,
                sourceDisplay: undefined,
                uri: fileNameToAbsoluteUri(filename)
            } as CompletionEntryWithIdentifier);
        });

        it('resolve completion and provide documentation', async () => {
            const { completionProvider, document } = setup('../documentation.svelte');

            const { documentation, detail } = await completionProvider.resolveCompletion(document, {
                label: 'foo',
                kind: 6,
                commitCharacters: ['.', ',', '('],
                data: {
                    name: 'foo',
                    kind: ts.ScriptElementKind.alias,
                    sortText: '0',
                    uri: '',
                    position: Position.create(3, 7)
                }
            });

            assert.deepStrictEqual(detail, '(alias) function foo(): boolean\nimport foo');
            assert.deepStrictEqual(documentation, {
                value: 'bars\n\n*@author* — John',
                kind: MarkupKind.Markdown
            });
        });

        it('provides import completions for directory', async () => {
            const { completionProvider, document } = setup('importcompletions.svelte');
            const mockDirName = 'foo';
            const mockDirPath = join(testFilesDir, mockDirName);

            mkdirSync(mockDirPath);

            try {
                const completions = await completionProvider.getCompletions(
                    document,
                    Position.create(0, 27),
                    {
                        triggerKind: CompletionTriggerKind.TriggerCharacter,
                        triggerCharacter: '/'
                    }
                );
                const mockedDirImportCompletion = completions?.items.find(
                    (item) => item.label === mockDirName
                );

                assert.notEqual(
                    mockedDirImportCompletion,
                    undefined,
                    "can't provide completions on directory"
                );
                assert.equal(mockedDirImportCompletion?.kind, CompletionItemKind.Folder);
            } finally {
                rmdirSync(mockDirPath);
            }
        });

        it('provides import completions in file with uppercase directory', async () => {
            const { completionProvider, document } = setup('UpperCase/dirCasing.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(1, 22),
                {
                    triggerKind: CompletionTriggerKind.TriggerCharacter,
                    triggerCharacter: '/'
                }
            );

            assert.equal(completions?.items[0].label, 'toImport.ts');
        });

        it('provides import completions for supported files', async () => {
            const sourceFile = 'importcompletions.svelte';
            const { completionProvider, document } = setup(sourceFile);
            const supportedExtensions = [
                ts.Extension.Js,
                ts.Extension.Ts,
                ts.Extension.Dts,
                ts.Extension.Jsx,
                ts.Extension.Tsx,
                ts.Extension.Json,
                '.svelte'
            ];
            const ignores = ['tsconfig.json', sourceFile];

            const testfiles = readdirSync(testFilesDir, { withFileTypes: true })
                .filter(
                    (f) =>
                        f.isDirectory() ||
                        (supportedExtensions.includes(extname(f.name)) && !ignores.includes(f.name))
                )
                .map((f) => f.name);

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(0, 27),
                {
                    triggerKind: CompletionTriggerKind.TriggerCharacter,
                    triggerCharacter: '/'
                }
            );

            assert.deepStrictEqual(
                sortBy(
                    completions?.items.map((item) => item.label),
                    (x) => x
                ),
                sortBy(testfiles, (x) => x)
            );
        });

        it('resolve auto import completion (is first import in file)', async () => {
            const { completionProvider, document } = setup('importcompletions1.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(1, 3)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'blubb');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from ../definitions\nfunction blubb(): boolean'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `${newLine}import { blubb } from "../definitions";${newLine}${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(0, 8), Position.create(0, 8))
            );
        });

        it('resolve auto import completion (is second import in file)', async () => {
            const { completionProvider, document } = setup('importcompletions2.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(2, 3)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'blubb');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from ../definitions\nfunction blubb(): boolean'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                `import { blubb } from '../definitions';${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(2, 0), Position.create(2, 0))
            );
        });

        it('resolve auto import completion (importing in same line as first import)', async () => {
            const { completionProvider, document } = setup('importcompletions3.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(0, 42)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'blubb');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from ../definitions\nfunction blubb(): boolean'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                `${newLine}import { blubb } from '../definitions';${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(0, 8), Position.create(0, 8))
            );
        });

        it('resolve auto import completion (is second import, module-script present)', async () => {
            const { completionProvider, document } = setup('importcompletions7.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(7, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'onMount');
            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from svelte\nfunction onMount(fn: () => any): void'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `${newLine}import { onMount } from "svelte";${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(4, 8), Position.create(4, 8))
            );
        });

        it('resolve auto import completion in instance script (instance and module script present)', async () => {
            const { completionProvider, document } = setup('importcompletions9.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'onMount');
            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `${newLine}import { onMount } from "svelte";${newLine}${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(4, 8), Position.create(4, 8))
            );
        });

        it('resolve auto import completion in module script (instance and module script present)', async () => {
            const { completionProvider, document } = setup('importcompletions9.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(1, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'onMount');
            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `${newLine}import { onMount } from "svelte";${newLine}${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(0, 25), Position.create(0, 25))
            );
        });

        async function openFileToBeImported(
            docManager: DocumentManager,
            completionProvider: CompletionsProviderImpl,
            name = 'imported-file.svelte'
        ) {
            const filePath = join(testFilesDir, name);
            const hoverinfoDoc = docManager.openDocument(<any>{
                uri: pathToUrl(filePath),
                text: ts.sys.readFile(filePath) || ''
            });
            await completionProvider.getCompletions(hoverinfoDoc, Position.create(1, 1));
        }

        it('resolve auto import completion (importing a svelte component)', async () => {
            const { completionProvider, document, docManager } = setup('importcompletions4.svelte');
            // make sure that the ts language service does know about the imported-file file
            await openFileToBeImported(docManager, completionProvider);

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(2, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'ImportedFile');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from ../imported-file.svelte\nclass ImportedFile'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `${newLine}import ImportedFile from "../imported-file.svelte";${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(0, 8), Position.create(0, 8))
            );
        });

        it('resolve auto import completion (importing a svelte component, no script tag yet)', async () => {
            const { completionProvider, document, docManager } = setup('importcompletions5.svelte');
            // make sure that the ts language service does know about the imported-file file
            await openFileToBeImported(docManager, completionProvider);

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(0, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'ImportedFile');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits, detail } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                detail,
                'Auto import from ../imported-file.svelte\nclass ImportedFile'
            );

            assert.strictEqual(
                harmonizeNewLines(additionalTextEdits![0]?.newText),
                // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
                `<script>${newLine}import ImportedFile from "../imported-file.svelte";` +
                    `${newLine}${newLine}</script>${newLine}`
            );

            assert.deepEqual(
                additionalTextEdits![0]?.range,
                Range.create(Position.create(0, 0), Position.create(0, 0))
            );
        });

        it('resolve auto completion without auto import (a svelte component which was already imported)', async () => {
            const { completionProvider, document, docManager } = setup('importcompletions6.svelte');
            // make sure that the ts language service does know about the imported-file file
            await openFileToBeImported(docManager, completionProvider);

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(3, 7)
            );
            document.version++;

            const item = completions?.items.find((item) => item.label === 'ImportedFile');

            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);

            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(additionalTextEdits, undefined);
        });

        it('doesnt suggest svelte auto import when already other import with same name present', async () => {
            const { completionProvider, document, docManager } = setup(
                'importcompletions-2nd-import.svelte'
            );
            // make sure that the ts language service does know about the imported-file file
            await openFileToBeImported(docManager, completionProvider, 'ScndImport.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(2, 13)
            );
            document.version++;

            const items = completions?.items.filter((item) => item.label === 'ScndImport');
            assert.equal(items?.length, 1);

            const item = items?.[0];
            assert.equal(item?.additionalTextEdits, undefined);
            assert.equal(item?.detail, undefined);
            assert.equal(item?.kind, CompletionItemKind.Variable);

            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(additionalTextEdits, undefined);
        });

        it('resolve auto completion in correct place when already imported in module script', async () => {
            const { completionProvider, document } = setup('importcompletions8.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 8)
            );

            const item = completions?.items.find((item) => item.label === 'blubb');

            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.deepStrictEqual(additionalTextEdits, <TextEdit[]>[
                {
                    newText: '{ blubb }',
                    range: Range.create(Position.create(1, 11), Position.create(1, 14))
                }
            ]);
        });

        it('can be canceled before promise resolved', async () => {
            const { completionProvider, document } = setup('importcompletions1.svelte');
            const cancellationTokenSource = new CancellationTokenSource();

            const completionsPromise = completionProvider.getCompletions(
                document,
                Position.create(1, 3),
                undefined,
                cancellationTokenSource.token
            );

            cancellationTokenSource.cancel();

            assert.deepStrictEqual(await completionsPromise, null);
        });

        it('can cancel completion resolving before promise resolved', async () => {
            const { completionProvider, document } = setup('importcompletions1.svelte');
            const cancellationTokenSource = new CancellationTokenSource();

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(1, 3)
            );

            const item = completions?.items.find((item) => item.label === 'blubb');

            const completionResolvingPromise = completionProvider.resolveCompletion(
                document,
                item!,
                cancellationTokenSource.token
            );
            cancellationTokenSource.cancel();

            assert.deepStrictEqual(
                (await completionResolvingPromise).additionalTextEdits,
                undefined
            );
        });

        const testForJsDocTemplateCompletion = async (position: Position, newText: string) => {
            const { completionProvider, document } = setup('jsdoc-completions.svelte');

            const completions = await completionProvider.getCompletions(document, position, {
                triggerKind: CompletionTriggerKind.TriggerCharacter,
                triggerCharacter: '*'
            });

            const item = completions?.items?.[0];
            const { line, character } = position;
            const start = Position.create(line, character - '/**'.length);
            const end = Position.create(line, character + '*/'.length);

            assert.strictEqual(harmonizeNewLines(item?.textEdit?.newText), newText);
            assert.deepStrictEqual((item?.textEdit as TextEdit)?.range, Range.create(start, end));
        };

        it('show jsDoc template completion', async () => {
            await testForJsDocTemplateCompletion(
                Position.create(1, 7),
                `/**${newLine} * $0${newLine} */`
            );
        });

        it('show jsDoc template completion on function', async () => {
            await testForJsDocTemplateCompletion(
                Position.create(4, 7),
                `/**${newLine} * $0${newLine} * @param parameter1${newLine} */`
            );
        });

        it('shows completions in reactive statement', async () => {
            const { completionProvider, document } = setup(
                'completions-in-reactive-statement.svelte'
            );

            await checkCompletion(Position.create(9, 13));
            await checkCompletion(Position.create(10, 16));
            await checkCompletion(Position.create(11, 14));
            await checkCompletion(Position.create(13, 17));
            await checkCompletion(Position.create(14, 20));
            await checkCompletion(Position.create(15, 18));

            async function checkCompletion(position: Position) {
                const completions = await completionProvider.getCompletions(document, position, {
                    triggerKind: CompletionTriggerKind.Invoked
                });
                assert.strictEqual(completions?.items.length, 1);
                const item = completions?.items?.[0];
                assert.strictEqual(item?.label, 'abc');
            }
        }).timeout(4000);

        it('provides default slot-let completion for components with type definition', async () => {
            const { completionProvider, document } = setup(
                'component-events-completion-ts-def.svelte'
            );

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(5, 17),
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const slotLetCompletions = completions!.items.filter((item) =>
                item.label.startsWith('let:')
            );

            assert.deepStrictEqual(slotLetCompletions, <CompletionItem[]>[
                {
                    detail: 'let1: boolean',
                    documentation: '',
                    label: 'let:let1',
                    sortText: '-1',
                    textEdit: {
                        newText: 'let:let1',
                        range: {
                            end: {
                                character: 17,
                                line: 5
                            },
                            start: {
                                character: 14,
                                line: 5
                            }
                        }
                    }
                },
                {
                    detail: 'let2: string',
                    documentation: {
                        kind: 'markdown',
                        value: 'documentation for let2'
                    },
                    label: 'let:let2',
                    sortText: '-1',
                    textEdit: {
                        newText: 'let:let2',
                        range: {
                            end: {
                                character: 17,
                                line: 5
                            },
                            start: {
                                character: 14,
                                line: 5
                            }
                        }
                    }
                }
            ]);
        });

        it('provides import statement completion', async () => {
            const { completionProvider, document } = setup('importstatementcompletions.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                {
                    line: 1,
                    character: 14
                },
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const item = completions?.items.find((item) => item.label === 'blubb');

            delete item?.data;

            assert.deepStrictEqual(item, {
                additionalTextEdits: [
                    {
                        newText: 'import ',
                        range: {
                            end: {
                                character: 11,
                                line: 1
                            },
                            start: {
                                character: 4,
                                line: 1
                            }
                        }
                    }
                ],
                label: 'blubb',
                insertText: 'import { blubb } from "../definitions";',
                kind: CompletionItemKind.Function,
                sortText: '11',
                commitCharacters: ['.', ',', '('],
                preselect: undefined,
                textEdit: {
                    newText: '{ blubb } from "../definitions";',
                    range: {
                        end: {
                            character: 15,
                            line: 1
                        },
                        start: {
                            character: 11,
                            line: 1
                        }
                    }
                }
            });
        });

        it('provides optional chaining completion', async () => {
            const { completionProvider, document } = setup(
                'completions-auto-optional-chain.svelte'
            );

            const completions = await completionProvider.getCompletions(
                document,
                {
                    line: 3,
                    character: 6
                },
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const item = completions?.items.find((item) => item.label === 'toString');

            delete item?.data;

            assert.deepStrictEqual(item, {
                additionalTextEdits: [
                    {
                        newText: '?',
                        range: {
                            end: {
                                character: 6,
                                line: 3
                            },
                            start: {
                                character: 5,
                                line: 3
                            }
                        }
                    }
                ],
                label: 'toString',
                insertText: '?.toString',
                kind: CompletionItemKind.Method,
                sortText: '11',
                commitCharacters: ['.', ',', '('],
                preselect: undefined,
                textEdit: {
                    newText: '.toString',
                    range: {
                        end: {
                            character: 6,
                            line: 3
                        },
                        start: {
                            character: 6,
                            line: 3
                        }
                    }
                }
            });
        });

        it('provide replacement for string completions', async () => {
            const { completionProvider, document } = setup('string-completion.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                {
                    line: 1,
                    character: 10
                },
                {
                    triggerKind: CompletionTriggerKind.Invoked
                }
            );

            const item = completions?.items.find((item) => item.label === '@hi');

            delete item?.data;

            assert.deepStrictEqual(item, {
                label: '@hi',
                kind: CompletionItemKind.Constant,
                sortText: '11',
                preselect: undefined,
                insertText: undefined,
                commitCharacters: undefined,
                textEdit: {
                    newText: '@hi',
                    range: {
                        end: {
                            character: 10,
                            line: 1
                        },
                        start: {
                            character: 9,
                            line: 1
                        }
                    }
                }
            });
        });

        it('auto import with system new line', async () => {
            const { completionProvider, document } = setup('importcompletions-new-line.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(1, 7)
            );

            const items = completions?.items.filter((item) => item.label === 'ScndImport');
            const item = items?.[0];

            const { additionalTextEdits } = await completionProvider.resolveCompletion(
                document,
                item!
            );

            assert.strictEqual(
                additionalTextEdits?.[0].newText,
                `${newLine}import { ScndImport } from "./to-import";${newLine}${newLine}`
            );
        });

        it('shouldnt do completions in text', async () => {
            const { completionProvider, document } = setup('importcompletions-text.svelte');

            await expectNoCompletions(4, 1);
            await expectNoCompletions(5, 5);
            await expectNoCompletions(5, 6);
            await expectNoCompletions(6, 0);
            await expectNoCompletions(6, 1);
            await expectNoCompletions(7, 5);
            await expectNoCompletions(8, 7);
            await expectNoCompletions(8, 8);
            await expectNoCompletions(9, 0);
            await expectNoCompletions(9, 1);
            await expectNoCompletions(10, 6);

            async function expectNoCompletions(line: number, char: number) {
                const completions = await completionProvider.getCompletions(
                    document,
                    Position.create(line, char)
                );
                assert.strictEqual(
                    completions,
                    null,
                    `expected no completions for ${line},${char}`
                );
            }
        });

        it('handles completion in empty text attribute', async () => {
            const { completionProvider, document } = setup('emptytext-importer.svelte');

            const completions = await completionProvider.getCompletions(
                document,
                Position.create(4, 14)
            );
            assert.deepStrictEqual(
                completions?.items.map((item) => item.label),
                ['s', 'm', 'l']
            );
        });

        // Hacky, but it works. Needed due to testing both new and old transformation
        after(() => {
            __resetCache();
        });
    };
}