Compare commits

...

5 Commits

Author SHA1 Message Date
f0256a0535 As alway, prettier has opinions 2024-07-16 14:04:23 -07:00
142a985914 Tightened the language. 2024-07-16 13:56:53 -07:00
a8531d498a Tests are updated and working. Had to revise the 'search-select' binding to work with the new search-select. 2024-07-16 13:49:17 -07:00
f8cb4e880b web: roll back update to sonar
Bloody dependabot.  We're not compatible with ESLint 9 yet, darnit, and yet
dependabot keeps pushing upgrades on us.
2024-07-16 09:23:28 -07:00
3ced637db3 web: grammar fix and lint update
1. Merged the two SonarJS lints into one
2. Fixed a grammatical error in RedirectStage
2024-07-16 09:19:57 -07:00
18 changed files with 99 additions and 107 deletions

View File

@ -53,7 +53,7 @@ type Pair = [string, string];
// Define a getter for each provider type in the radio button collection.
const providerValues: Pair[] = [
["oauth2provider", "oauth2Provider"],
["oauth2Provider", "oauth2Provider"],
["ldapprovider", "ldapProvider"],
["proxyprovider-proxy", "proxyProviderProxy"],
["proxyprovider-forwardsingle", "proxyProviderForwardsingle"],
@ -66,7 +66,7 @@ providerValues.forEach(([value, name]: Pair) => {
Object.defineProperties(ApplicationWizardView.prototype, {
[name]: {
get: function () {
return this.providerList.$(`>>>input[value="${value}"]`);
return this.providerList.$(`>>>div[data-testid=wizard-provider-${value}]`);
},
},
});

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class ForwardProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
'[name="authorizationFlow"]',
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}

View File

@ -3,9 +3,9 @@ import Page from "../page.js";
export class LdapForm extends Page {
async setBindFlow(selector: string) {
await this.searchSelect(
'>>>ak-branded-flow-search[name="authorizationFlow"] input[type="text"]',
"[name=authorizationFlow]",
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}
}

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class OauthForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
'[name="authorizationFlow"]',
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}

View File

@ -3,9 +3,9 @@ import Page from "../page.js";
export class RadiusForm extends Page {
async setAuthenticationFlow(selector: string) {
await this.searchSelect(
'>>>ak-branded-flow-search[name="authorizationFlow"] input[type="text"]',
'[name="authorizationFlow"]',
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}
}

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class SamlForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
'[name="authorizationFlow"]',
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class TransparentProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
'[name="authorizationFlow"]',
"authorizationFlow",
`button*=${selector}`,
`div*=${selector}`,
);
}

View File

@ -1,6 +1,7 @@
import Page from "./page.js";
import UserLibraryPage from "./user-library.page.js";
import { $ } from "@wdio/globals";
import { and, or, presenceOf, textToBePresentInElement } from "wdio-wait-for";
/**
* sub page containing specific selectors and methods for a specific page
@ -48,6 +49,14 @@ class LoginPage extends Page {
await this.pause();
await this.password(password);
await this.pause();
const redirect = await $(">>>a[type=submit]");
await browser.waitUntil(or(presenceOf(redirect), presenceOf(UserLibraryPage.pageHeader)));
if (await redirect.isExisting()) {
await redirect.click();
}
await this.pause(">>>div.header h1");
return UserLibraryPage;
}

View File

@ -32,10 +32,16 @@ export default class Page {
*/
async searchSelect(searchSelector: string, managedSelector: string, buttonSelector: string) {
const inputBind = await $(searchSelector);
const controlSelector = `>>>ak-search-select-view${searchSelector}`;
const control = await $(controlSelector);
control.scrollIntoView();
const inputBind = await control.$(">>>input[type=text]");
await inputBind.click();
const searchBlock = await $(`>>>div[data-managed-for="${managedSelector}"]`);
const target = searchBlock.$(buttonSelector);
const interior = searchBlock.$(">>>ul");
interior.scrollIntoView();
const target = interior.$(buttonSelector);
return await target.click();
}

View File

@ -1,10 +1,7 @@
import LoginPage from "../pageobjects/login.page.js";
import UserLibraryPage from "../pageobjects/user-library.page.js";
import { GOOD_PASSWORD, GOOD_USERNAME } from "./constants.js";
import { expect } from "@wdio/globals";
export const login = async () => {
await LoginPage.open();
await LoginPage.login(GOOD_USERNAME, GOOD_PASSWORD);
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
};

View File

@ -212,7 +212,9 @@ export const config: Options.Testrunner = {
* @param {Array.<String>} specs List of spec file paths that are to be run
* @param {object} browser instance of created browser/device session
*/
before: function (_capabilities, _specs) {},
before: function (_capabilities, _specs) {
browser.setWindowSize(1920, 1080);
},
/**
* Runs before a WebdriverIO command gets executed.
* @param {string} commandName hook command name

10
web/package-lock.json generated
View File

@ -93,7 +93,7 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^1.0.3",
"eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
@ -14421,15 +14421,15 @@
"license": "MIT"
},
"node_modules/eslint-plugin-sonarjs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.3.tgz",
"integrity": "sha512-6s41HLPYPyDrp+5+7Db5yFYbod6h9pC7yx+xfcNwHRcLe1EZwbbQT/tdOAkR7ekVUkNGEvN3GmYakIoQUX7dEg==",
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.1.tgz",
"integrity": "sha512-5IOKvj/GMBNqjxBdItfotfRHo7w48496GOu1hxdeXuD0mB1JBlDCViiLHETDTfA8pDAVSBimBEQoetRXYceQEw==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"eslint": "^8.0.0 || ^9.0.0"
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/eslint-plugin-storybook": {

View File

@ -120,7 +120,7 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^1.0.3",
"eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",

View File

@ -1,55 +0,0 @@
import { execFileSync } from "child_process";
import { ESLint } from "eslint";
import path from "path";
import process from "process";
// Code assumes this script is in the './web/scripts' folder.
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
}).replace("\n", "");
process.chdir(path.join(projectRoot, "./web"));
const eslintConfig = {
overrideConfig: {
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:lit/recommended",
"plugin:custom-elements/recommended",
"plugin:storybook/recommended",
"plugin:sonarjs/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
rules: {
"indent": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { avoidEscape: true }],
"semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off",
"sonarjs/cognitive-complexity": ["error", 9],
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off",
},
},
};
const updated = ["./src/", "./build.mjs", "./scripts/*.mjs"];
const eslint = new ESLint(eslintConfig);
const results = await eslint.lintFiles(updated);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
// eslint-disable-next-line no-console
console.log(resultText);
process.exit(errors > 1 ? 1 : 0);

View File

@ -43,34 +43,53 @@ const eslintConfig = {
},
};
const porcelainV1 = /^(..)\s+(.*$)/;
const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" });
function findChangedFiles() {
const porcelainV1 = /^(..)\s+(.*$)/;
const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" });
const statuses = gitStatus.split("\n").reduce((acc, line) => {
const match = porcelainV1.exec(line.replace("\n"));
if (!match) {
return acc;
}
const [status, path] = Array.from(match).slice(1, 3);
return [...acc, [status, path.split("\x00")[0]]];
}, []);
const statuses = gitStatus.split("\n").reduce((acc, line) => {
const match = porcelainV1.exec(line.replace("\n"));
if (!match) {
return acc;
}
const [status, path] = Array.from(match).slice(1, 3);
return [...acc, [status, path.split("\x00")[0]]];
}, []);
const isModified = /^(M|\?|\s)(M|\?|\s)/;
const modified = (s) => isModified.test(s);
const isModified = /^(M|\?|\s)(M|\?|\s)/;
const modified = (s) => isModified.test(s);
const isCheckable = /\.(ts|js|mjs)$/;
const checkable = (s) => isCheckable.test(s);
const isCheckable = /\.(ts|js|mjs)$/;
const checkable = (s) => isCheckable.test(s);
const ignored = /\/\.storybook\//;
const notIgnored = (s) => !ignored.test(s);
const ignored = /\/\.storybook\//;
const notIgnored = (s) => !ignored.test(s);
const updated = statuses.reduce(
(acc, [status, filename]) =>
modified(status) && checkable(filename) && notIgnored(filename)
? [...acc, path.join(projectRoot, filename)]
: acc,
[],
);
return statuses.reduce(
(acc, [status, filename]) =>
modified(status) && checkable(filename) && notIgnored(filename)
? [...acc, path.join(projectRoot, filename)]
: acc,
[],
);
}
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
// eslint-disable-next-line no-console
console.log(`Run eslint with extra-hard checks
options:
-c, --changed: (default) check only the files that have changed
-n, --nightmare: check all the files in the repository
-h, --help: This help message
`);
process.exit(0);
}
const updated =
process.argv.length > 2 && (process.argv[2] === "-n" || process.argv[2] === "--nightmare")
? ["./src/", "./build.mjs", "./scripts/*.mjs"]
: findChangedFiles();
const eslint = new ESLint(eslintConfig);
const results = await eslint.lintFiles(updated);

View File

@ -30,6 +30,7 @@ export type LocalTypeCreate = TypeCreate & {
modelName: ProviderModelEnumType;
converter: ModelConverter;
note?: ProviderNote;
testId: string;
renderer: ProviderRenderer;
};
@ -46,6 +47,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as OAuth2ProviderRequest),
}),
component: "",
testId: "wizard-provider-oauth2Provider",
iconUrl: "/static/authentik/sources/openidconnect.svg",
},
{
@ -62,6 +64,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as LDAPProviderRequest),
}),
component: "",
testId: "wizard-provider-ldapprovider",
iconUrl: "/static/authentik/sources/ldap.png",
},
{
@ -77,6 +80,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.Proxy,
}),
component: "",
testId: "wizard-provider-proxyprovider-proxy",
iconUrl: "/static/authentik/sources/proxy.svg",
},
{
@ -92,6 +96,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.ForwardSingle,
}),
component: "",
testId: "wizard-provider-proxyprovider-forwardsingle",
iconUrl: "/static/authentik/sources/proxy.svg",
},
{
@ -107,6 +112,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.ForwardDomain,
}),
component: "",
testId: "wizard-provider-proxyprovider-forwarddomain",
iconUrl: "/static/authentik/sources/proxy.svg",
},
{
@ -123,6 +129,7 @@ export const providerModelsList: LocalTypeCreate[] = [
note: () => html`<ak-license-notice></ak-license-notice>`,
requiresEnterprise: true,
component: "",
testId: "wizard-provider-racprovider",
iconUrl: "/static/authentik/sources/rac.svg",
},
{
@ -137,6 +144,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as SAMLProviderRequest),
}),
component: "",
testId: "wizard-provider-samlprovider",
iconUrl: "/static/authentik/sources/saml.png",
},
{
@ -151,6 +159,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as RadiusProviderRequest),
}),
component: "",
testId: "wizard-provider-radiusprovider",
iconUrl: "/static/authentik/sources/radius.svg",
},
{
@ -165,6 +174,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as SCIMProviderRequest),
}),
component: "",
testId: "wizard-provider-scimprovider",
iconUrl: "/static/authentik/sources/scim.png",
},
];

View File

@ -5,6 +5,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -19,10 +20,12 @@ export enum TypeCreateWizardPageLayouts {
grid = "grid",
}
type TypeCreateWithTestId = TypeCreate & { testId?: string };
@customElement("ak-wizard-page-type-create")
export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
@property({ attribute: false })
types: TypeCreate[] = [];
types: TypeCreateWithTestId[] = [];
@property({ attribute: false })
selectedType?: TypeCreate;
@ -51,7 +54,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
sidebarLabel = () => msg("Select type");
activeCallback: () => Promise<void> = async () => {
activeCallback = async () => {
this.host.isValid = false;
if (this.selectedType) {
this.selectDispatch(this.selectedType);
@ -78,6 +81,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
: "pf-m-selectable-raised"} ${this.selectedType == type
? "pf-m-selected-raised"
: ""}"
data-testid=${ifDefined(type.testId)}
tabindex=${idx}
@click=${() => {
if (requiresEnterprise) {

View File

@ -79,7 +79,7 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
<div class="pf-c-login__main-body">
<form class="pf-c-form">
<div class="pf-c-form__group">
<p>${msg("You're about to be redirect to the following URL.")}</p>
<p>${msg("You will now be redirected to the following URL.")}</p>
<code>${this.getURL()}</code>
</div>
<div class="pf-c-form__group pf-m-action">