Skip to content

Commit

Permalink
Adds listing of tests based on files in a directory.
Browse files Browse the repository at this point in the history
Adds the option to have the result in two formats.
  • Loading branch information
renatoliveira committed Sep 6, 2024
1 parent d0fa082 commit bb70d25
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 176 deletions.
145 changes: 7 additions & 138 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,145 +1,14 @@
# apextestlist
# Apex Test List

[![NPM](https://img.shields.io/npm/v/apextestlist.svg?label=apextestlist)](https://www.npmjs.com/package/apextestlist) [![Downloads/week](https://img.shields.io/npm/dw/apextestlist.svg)](https://npmjs.org/package/apextestlist) [![License](https://img.shields.io/badge/License-BSD%203--Clause-brightgreen.svg)](https://raw.githubusercontent.com/salesforcecli/apextestlist/main/LICENSE.txt)

## Using the template
A plugin that generates a list of tests that your, ideally, automated process should run, so you can save time by not running all tests in your Salesforce org and also save time by not specifying them manually.

This repository provides a template for creating a plugin for the Salesforce CLI. To convert this template to a working plugin:
## Usage

1. Please get in touch with the Platform CLI team. We want to help you develop your plugin.
2. Generate your plugin:
List all files specified in the classes of a certain directory (and subfolders) and have the result be in the format for the CLI.

```
sf plugins install dev
sf dev generate plugin
git init -b main
git add . && git commit -m "chore: initial commit"
```

3. Create your plugin's repo in the salesforcecli github org
4. When you're ready, replace the contents of this README with the information you want.

## Learn about `sf` plugins

Salesforce CLI plugins are based on the [oclif plugin framework](<(https://oclif.io/docs/introduction.html)>). Read the [plugin developer guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_architecture_sf_cli.htm) to learn about Salesforce CLI plugin development.

This repository contains a lot of additional scripts and tools to help with general Salesforce node development and enforce coding standards. You should familiarize yourself with some of the [node developer packages](#tooling) used by Salesforce.

Additionally, there are some additional tests that the Salesforce CLI will enforce if this plugin is ever bundled with the CLI. These test are included by default under the `posttest` script and it is required to keep these tests active in your plugin if you plan to have it bundled.

### Tooling

- [@salesforce/core](https://github.com/forcedotcom/sfdx-core)
- [@salesforce/kit](https://github.com/forcedotcom/kit)
- [@salesforce/sf-plugins-core](https://github.com/salesforcecli/sf-plugins-core)
- [@salesforce/ts-types](https://github.com/forcedotcom/ts-types)
- [@salesforce/ts-sinon](https://github.com/forcedotcom/ts-sinon)
- [@salesforce/dev-config](https://github.com/forcedotcom/dev-config)
- [@salesforce/dev-scripts](https://github.com/forcedotcom/dev-scripts)

### Hooks

For cross clouds commands, e.g. `sf env list`, we utilize [oclif hooks](https://oclif.io/docs/hooks) to get the relevant information from installed plugins.

This plugin includes sample hooks in the [src/hooks directory](src/hooks). You'll just need to add the appropriate logic. You can also delete any of the hooks if they aren't required for your plugin.

# Everything past here is only a suggestion as to what should be in your specific plugin's description

This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm).

We always recommend using the latest version of these commands bundled with the CLI, however, you can install a specific version or tag if needed.

## Install

```bash
sf plugins install [email protected]
```

## Issues

Please report any issues at https://github.com/forcedotcom/cli/issues

## Contributing

1. Please read our [Code of Conduct](CODE_OF_CONDUCT.md)
2. Create a new issue before starting your project so that we can keep track of
what you are trying to add/fix. That way, we can also offer suggestions or
let you know if there is already an effort in progress.
3. Fork this repository.
4. [Build the plugin locally](#build)
5. Create a _topic_ branch in your fork. Note, this step is recommended but technically not required if contributing using a fork.
6. Edit the code in your fork.
7. Write appropriate tests for your changes. Try to achieve at least 95% code coverage on any new code. No pull request will be accepted without unit tests.
8. Sign CLA (see [CLA](#cla) below).
9. Send us a pull request when you are done. We'll review your code, suggest any needed changes, and merge it in.

### CLA

External contributors will be required to sign a Contributor's License
Agreement. You can do so by going to https://cla.salesforce.com/sign-cla.

### Build

To build the plugin locally, make sure to have yarn installed and run the following commands:

```bash
# Clone the repository
git clone [email protected]:salesforcecli/apextestlist

# Install the dependencies and compile
yarn && yarn build
```sh
sf apextests list --directory samples --format sf
$ --tests SampleTest SuperSampleTest Sample2Test SuperSample2Test
```

To use your plugin, run using the local `./bin/dev` or `./bin/dev.cmd` file.

```bash
# Run using local run file.
./bin/dev hello world
```

There should be no differences when running via the Salesforce CLI or using the local run file. However, it can be useful to link the plugin to do some additional testing or run your commands from anywhere on your machine.

```bash
# Link your plugin to the sf cli
sf plugins link .
# To verify
sf plugins
```

## Commands

<!-- commands -->

- [`sf hello world`](#sf-hello-world)

## `sf hello world`

Say hello either to the world or someone you know.

```
USAGE
$ sf hello world [--json] [-n <value>]
FLAGS
-n, --name=<value> [default: World] The name of the person you'd like to say hello to.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Say hello either to the world or someone you know.
Say hello either to the world or someone you know.
EXAMPLES
Say hello to the world:
$ sf hello world
Say hello to someone you know:
$ sf hello world --name Astro
```

<!-- commandsstop -->
11 changes: 6 additions & 5 deletions messages/apextests.list.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ Directory name. Defaults to the current working directory.

Usually, if you run this command at the root of your Salesforce DX project, it means that the directory points to the original "force-app" folder (even if you have renamed it to something else). If you wish to limit the usage to a specific module, specify the folder with this flag.

# flags.manifest.summary
# flags.format.summary

Manifest file.
Format of the output.

# flags.manifest.description
# flags.format.description

Points to the manifest XML file of your deployment (the `package.xml` file).
By default, the format being returned is a list in the format that can be merged with the test flags of the Salesforce CLI deploy and validate commands. Available formats are `sf` (default) and `csv`.

# examples

- <%= config.bin %> <%= command.id %>
- <%= config.bin %> <%= command.id %> --format csv
- <%= config.bin %> <%= command.id %> --format sf --directory force-app
102 changes: 69 additions & 33 deletions src/commands/apextests/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('apextestlist', 'apextests.list');

export type ApextestsListResult = {
files: string[];
tests: string[];
command: string;
// TODO: in the future, return the test suites as well
};

export default class ApextestsList extends SfCommand<ApextestsListResult> {
Expand All @@ -21,12 +23,13 @@ export default class ApextestsList extends SfCommand<ApextestsListResult> {
char: 'd',
required: false,
}),
manifest: Flags.string({
summary: messages.getMessage('flags.manifest.summary'),
description: messages.getMessage('flags.manifest.description'),
char: 'x',
format: Flags.string({
summary: messages.getMessage('flags.format.summary'),
description: messages.getMessage('flags.format.description'),
char: 'f',
required: false,
}),
// TODO: add manifest flag
};

private static parseTestsNames(data: string): string[] {
Expand All @@ -35,48 +38,81 @@ export default class ApextestsList extends SfCommand<ApextestsListResult> {
.split(',')
.map((line) => line.replace(/(@Tests|@TestSuites)/, ''))
.map((line) => line.replace(':', ''))
.map((line) => line.trim())
.filter((line) => line.trim().length > 0);
}

private static listTestsInDirectory(directory: string): string[] {
// check if the provided directory exists
if (!directory) {
throw new Error('Invalid directory.');
}

let readDir;

try {
readDir = fs.readdirSync(directory, { recursive: true }) as string[];
} catch (error) {
throw new Error('Invalid directory.');
}

const files = readDir.filter((file) => file.endsWith('.cls'));
const testMethodsNames: string[] = [];

// read each file and check for the test methods at the top
files.map((file) => {
const data = fs.readFileSync(`${directory}/${file}`, 'utf8');

// try to find, with a RegEx, the test methods listed at the top of the
// file with @Tests or @TestSuites
const testMethods = data.match(/(@(Tests|TestSuites)).+/g);
// for each entry, parse the names
// const testMethodsNames = testMethods ? ApextestsList.parseTestsNames(testMethods.join(',')) : [];
testMethodsNames.push(...(testMethods ? ApextestsList.parseTestsNames(testMethods.join(',')) : []));
});

return testMethodsNames;
}

private static async formatList(format: string, tests: string[]): Promise<ApextestsListResult> {
switch (format) {
case 'sf':
return Promise.resolve({
tests,
command: '--tests ' + tests.join(' '),
});
case 'csv':
return Promise.resolve({
tests,
command: tests.join(','),
});
default:
throw new Error('Invalid format.');
}
}

public async run(): Promise<ApextestsListResult> {
const { flags } = await this.parse(ApextestsList);

const directory = flags.directory ?? '.';
const manifest = flags.manifest;
const format = flags.format ?? 'sf';

if (directory && manifest) {
// throw an error because both flags can't be used together
throw new Error('Both directory and manifest flags cannot be used together');
if (!directory) {
throw new Error('Directory must be provided.');
}

let result: Promise<ApextestsListResult> | null = null;

if (directory) {
return this.listTestsInDirectory(directory);
result = ApextestsList.formatList(format, ApextestsList.listTestsInDirectory(directory));
}

throw new Error('No directory or manifest provided');
}

private listTestsInDirectory(directory: string): Promise<ApextestsListResult> {
// check if the provided directory exists
if (directory) {
const readDir = fs.readdirSync(directory, { recursive: true }) as string[];
const files = readDir.filter((file) => file.endsWith('.cls'));

// read each file and check for the test methods at the top
files.map((file) => {
const data = fs.readFileSync(`${directory}/${file}`, 'utf8');

// try to find, with a RegEx, the test methods listed at the top of the
// file with @Tests or @TestSuites
const testMethods = data.match(/(@(Tests|TestSuites)).+/g);
// for each entry, parse the names
const testMethodsNames = testMethods ? ApextestsList.parseTestsNames(testMethods.join(',')) : [];
this.log(testMethodsNames.join(','));
});
if (!result) {
throw new Error('No directory or manifest provided');
}

return Promise.resolve({
files: [],
});
this.log((await result).command);

return result;
}
}

0 comments on commit bb70d25

Please sign in to comment.