Refine the 'New Item' screen (#26031)

* Refine the 'New Item' screen

* Simplify SCSS

* Fix submission, fix some tests, improve tabbing + focus states

* Fix more tests

* Simplify JS + hopefully fix test

* Update pom.xml

* Update Jenkinsfile

* Fix issue noticed in #26069

* Restore Jenkinsfile/test pom

* Simplify JS

* Restore ability to have headings

* Fix focus state in Firefox/Chrome

* Add separator between items and 'Duplicate an item'
This commit is contained in:
Jan Faracik authored and GitHub committed 2026-02-09 11:13:17 -07:00
1 parent 4daf33db48
commit f972b9d819
13 files changed
+381 -328

No files matched your search

@@ -35,35 +35,48 @@ THE SOFTWARE.
<l:breadcrumb title="${%NewJob(it.newPronoun)}" />
<l:main-panel>
<template id="i18n" data-empty-name="${%ItemName.validation.required}" />
<div id="add-item-panel" style="display: none;">
<h1>${%NewJob(it.newPronoun)}</h1>
<l:app-bar title="${%NewJob(it.newPronoun)}" />
<form method="post" action="createItem" name="createItem" id="createItem">
<div class="header">
<div class="add-item-name">
<div class="jenkins-form-item add-item-name">
<label for="name" class="jenkins-form-label">${%ItemName.label}</label>
<input name="name" class="jenkins-input" id="name" data-valid="false" type="text" tabindex="0" />
<div id="itemname-required" class="input-validation-message input-message-disabled">&#187; ${%ItemName.validation.required}</div>
<div id="itemname-invalid" class="input-validation-message input-message-disabled"></div>
<div id="itemtype-required" class="input-validation-message input-message-disabled">&#187; ${%ItemType.validation.required}</div>
<div class="validation-error-area" />
</div>
</div>
<div>
<div class="jenkins-form-label">${%ItemType.label}</div>
<div id="items" class="categories flat" role="radiogroup" aria-labelledby="Items" data-valid="false" />
</div>
<fieldset class="jenkins-!-display-contents">
<div class="jenkins-form-item">
<legend class="jenkins-form-label">${%ItemType.label}</legend>
<div id="items" class="jenkins-choice-list categories" data-valid="false" />
<j:if test="${!empty(app.items)}">
<div class="item-copy">
<p class="jenkins-form-label">${%CopyOption.description}</p>
<div class="add-item-copy">
<input type="radio" name="mode" value="copy" />
<label>${%CopyOption.label}</label>
<j:set var="descriptor" value="${it.descriptor}" />
<s:textbox id="from" data-valid="false" name="from" placeholder="${%CopyOption.placeholder}" field="copyNewItemFrom"/>
</div>
<j:if test="${!empty(app.items)}">
<div class="jenkins-choice-list jenkins-!-margin-top-2">
<div class="jenkins-choice-list__item">
<label id="duplicate-job">
<div class="jenkins-choice-list__item__icon">
<l:icon src="symbol-duplicate" />
</div>
<input type="radio" name="mode" value="copy" />
<div class="jenkins-choice-list__item__label">
${%CopyOption.label}
</div>
<div class="jenkins-choice-list__item__description">${%CopyOption.description}</div>
</label>
<div class="jenkins-choice-list__item__conditional">
<j:set var="descriptor" value="${it.descriptor}" />
<s:textbox id="from" data-valid="false" name="from" placeholder="${%CopyOption.placeholder}" field="copyNewItemFrom"/>
</div>
</div>
</div>
</j:if>
</div>
</j:if>
</fieldset>
<s:bottomButtonBar>
<st:include page="newJobButtonBar.jelly"/>
@@ -6,7 +6,7 @@ ItemType.label=Select an item type
ItemName.validation.required=This field cannot be empty, please enter a valid name
ItemType.validation.required=Please select an item type
CopyOption.placeholder=Type to autocomplete
CopyOption.description=If you want to create a new item from other existing, you can use this option:
CopyOption.label=Copy from
CopyOption.description=Create a new item by copying the configuration of an existing one.
CopyOption.label=Duplicate an existing item
CopyExisting=Copy existing {0}
+1
View File
@@ -77,6 +77,7 @@ module.exports = [
Utilities: "readonly",
UTILITIES_VERSION: "writeable",
YAHOO: "readonly",
updateValidationArea: "readonly",
},
},
},
+71 -122
View File
@@ -1,5 +1,7 @@
import { getI18n } from "@/util/i18n";
import { createElementFromHtml } from "@/util/dom";
const enableHeadings = false;
const nameInput = document.querySelector(`#createItem input[name="name"]`);
const copyFromInput = document.querySelector(`#createItem input[name="from"]`);
const copyRadio = document.querySelector(`#createItem input[value="copy"]`);
@@ -60,22 +62,25 @@ document.addEventListener("DOMContentLoaded", () => {
}
}
function activateValidationMessage(messageId, context, message) {
if (message !== undefined && message !== "") {
document.querySelector(context + " " + messageId).textContent =
"» " + message;
/**
* Shows or clears the validation message for the name input.
*
* Only updates the UI after the user has interacted with the input, which is
* indicated by `nameInput.dataset.dirty` being set.
*/
function activateValidationMessage(message) {
if (!nameInput.dataset.dirty) {
return;
}
cleanValidationMessages(context);
document
.querySelector(messageId)
.classList.remove("input-message-disabled");
refreshSubmitButtonState();
}
function cleanValidationMessages(context) {
document
.querySelectorAll(context + " .input-validation-message")
.forEach((element) => element.classList.add("input-message-disabled"));
updateValidationArea(
document.querySelector(".validation-error-area"),
message !== undefined && message !== ""
? `<div class="error">${message}</div>`
: `<div/>`,
);
refreshSubmitButtonState();
}
function refreshSubmitButtonState() {
@@ -96,15 +101,9 @@ document.addEventListener("DOMContentLoaded", () => {
}
function cleanItemSelection() {
document
.querySelector('.categories li[role="radio"]')
.setAttribute("aria-checked", "false");
document
.querySelector('#createItem input[type="radio"][name="mode"]')
.removeAttribute("checked");
document.querySelectorAll(".categories .active").forEach((item) => {
item.classList.remove("active");
});
setFieldValidationStatus("items", false);
}
@@ -120,42 +119,37 @@ document.addEventListener("DOMContentLoaded", () => {
// Draw functions
function drawCategory(category) {
var $category = createElementFromHtml("<div class='category' />");
$category.setAttribute(
"id",
"j-add-item-type-" + cleanClassName(category.id),
const heading = createElementFromHtml(
"<div class='jenkins-choice-list__heading'></div>",
);
var $items = createElementFromHtml(`<ul class="j-item-options" />`);
var $catHeader = createElementFromHtml(`<div class="header" />`);
var title = "<h2>" + category.name + "</h2>";
var description = "<p>" + category.description + "</p>";
const title = createElementFromHtml("<h2>" + category.name + "</h2>");
const description = createElementFromHtml(
"<p>" + category.description + "</p>",
);
heading.appendChild(title);
heading.appendChild(description);
const response = [];
if (enableHeadings) {
response.push(heading);
}
// Add items
category.items.forEach((elem) => {
$items.append(drawItem(elem));
response.push(drawItem(elem));
});
$catHeader.append(createElementFromHtml(title));
$catHeader.append(createElementFromHtml(description));
$category.append($catHeader);
$category.append($items);
return $category;
return response;
}
function drawItem(elem) {
var item = document.createElement("li");
item.tabIndex = 0;
item.className = cleanClassName(elem.class);
item.setAttribute("role", "radio");
item.setAttribute("aria-checked", "false");
var item = document.createElement("div");
item.className =
cleanClassName(elem.class) + " jenkins-choice-list__item";
var label = item.appendChild(document.createElement("label"));
var iconDiv = drawIcon(elem);
item.appendChild(iconDiv);
var labelContainer = document.createElement("div");
item.appendChild(labelContainer);
var label = labelContainer.appendChild(document.createElement("label"));
label.appendChild(iconDiv);
var radio = label.appendChild(document.createElement("input"));
radio.type = "radio";
@@ -163,36 +157,24 @@ document.addEventListener("DOMContentLoaded", () => {
radio.value = elem.class;
var displayName = label.appendChild(document.createElement("span"));
displayName.className = "label";
displayName.className = "jenkins-choice-list__item__label";
displayName.appendChild(document.createTextNode(elem.displayName));
var desc = labelContainer.appendChild(document.createElement("div"));
desc.className = "desc";
var desc = label.appendChild(document.createElement("div"));
desc.className = "jenkins-choice-list__item__description";
desc.innerHTML = checkForLink(elem.description);
function select(e) {
e.preventDefault();
function select() {
cleanCopyFromOption();
cleanItemSelection();
item.setAttribute("aria-checked", "true");
radio.checked = true;
item.classList.add("active");
setFieldValidationStatus("items", true);
if (getFieldValidationStatus("name")) {
refreshSubmitButtonState();
}
}
item.addEventListener("click", select);
item.addEventListener("keydown", function (evt) {
if (evt.code === "Space" || evt.code === "Enter") {
this.click();
evt.stopPropagation();
}
});
radio.addEventListener("change", select);
return item;
}
@@ -200,10 +182,10 @@ document.addEventListener("DOMContentLoaded", () => {
function drawIcon(elem) {
var iconDiv = document.createElement("div");
if (elem.iconXml) {
iconDiv.className = "icon";
iconDiv.className = "jenkins-choice-list__item__icon";
iconDiv.innerHTML = elem.iconXml;
} else if (elem.iconClassName && elem.iconQualifiedUrl) {
iconDiv.className = "icon";
iconDiv.className = "jenkins-choice-list__item__icon";
var img1 = document.createElement("img");
img1.src = elem.iconQualifiedUrl;
@@ -212,7 +194,7 @@ document.addEventListener("DOMContentLoaded", () => {
// Example for Freestyle project
// <div class="icon"><img class="icon-freestyle-project icon-xlg" src="/jenkins/static/108b2346/images/48x48/freestyleproject.png"></div>
} else if (elem.iconFilePathPattern) {
iconDiv.className = "icon";
iconDiv.className = "jenkins-choice-list__item__icon";
var iconFilePath =
jRoot + "/" + elem.iconFilePathPattern.replace(":size", "48x48");
@@ -238,7 +220,7 @@ document.addEventListener("DOMContentLoaded", () => {
spanFakeImgB.className = "b";
spanFakeImgB.innerText = b;
iconDiv.appendChild(spanFakeImgB);
iconDiv.className = "default-icon";
iconDiv.className = "jenkins-choice-list__item__icon";
// Example for MockFolder
// <div class="default-icon c-49728B"><span class="a">M</span><span class="b">o</span></div>
@@ -250,14 +232,11 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#add-item-panel").removeAttribute("style");
// Render all categories
var $categories = document.querySelector("div.categories");
var $categories = document.querySelector(".categories");
data.categories.forEach((elem) => {
$categories.append(drawCategory(elem));
drawCategory(elem).forEach((e) => $categories.append(e));
});
// Focus
document.querySelector("#add-item-panel #name").focus();
// Init NameField
function nameFieldEvent() {
if (!isItemNameEmpty()) {
@@ -268,13 +247,11 @@ document.addEventListener("DOMContentLoaded", () => {
response.text().then((data) => {
var message = parseResponseFromCheckJobName(data);
if (message !== "") {
activateValidationMessage(
"#itemname-invalid",
".add-item-name",
message,
);
activateValidationMessage(message);
setFieldValidationStatus("name", false);
refreshSubmitButtonState();
} else {
cleanValidationMessages(".add-item-name");
activateValidationMessage("");
setFieldValidationStatus("name", true);
refreshSubmitButtonState();
}
@@ -283,14 +260,16 @@ document.addEventListener("DOMContentLoaded", () => {
);
} else {
setFieldValidationStatus("name", false);
cleanValidationMessages(".add-item-name");
activateValidationMessage("#itemname-required", ".add-item-name");
activateValidationMessage(getI18n("empty-name"));
refreshSubmitButtonState();
}
}
nameInput.addEventListener("blur", nameFieldEvent);
nameInput.addEventListener("input", nameFieldEvent);
nameInput.addEventListener("input", () => {
nameInput.dataset.dirty = "true";
nameFieldEvent();
});
// Init CopyFromField
function copyFromFieldEvent() {
@@ -300,53 +279,23 @@ document.addEventListener("DOMContentLoaded", () => {
cleanItemSelection();
copyRadio.setAttribute("checked", true);
setFieldValidationStatus("from", true);
if (!getFieldValidationStatus("name")) {
activateValidationMessage("#itemname-required", ".add-item-name");
setTimeout(function () {
var parentName = copyFromInput.value;
fetch("job/" + parentName + "/api/json?tree=name").then(
(response) => {
response.json().then((data) => {
if (data.name === parentName) {
//if "name" is invalid, but "from" is a valid job, then switch focus to "name"
nameInput.focus();
}
});
},
);
}, 400);
} else {
refreshSubmitButtonState();
}
refreshSubmitButtonState();
}
}
copyFromInput?.addEventListener("blur", copyFromFieldEvent);
copyFromInput?.addEventListener("input", copyFromFieldEvent);
// Client-side validation
document
.querySelector("#createItem")
.addEventListener("submit", function (event) {
if (!getFormValidationStatus()) {
event.preventDefault();
if (!getFieldValidationStatus("name")) {
activateValidationMessage("#itemname-required", ".add-item-name");
nameInput.focus();
} else {
if (
!getFieldValidationStatus("items") &&
!getFieldValidationStatus("from")
) {
activateValidationMessage("#itemtype-required", ".add-item-name");
nameInput.focus();
}
}
}
});
// Focus the Name input on load
document.querySelector("#add-item-panel #name").focus();
// Disable the submit button
// Disable the Submit button on load
refreshSubmitButtonState();
});
if (copyRadio !== null) {
copyRadio.addEventListener("change", () => {
copyFromInput.focus();
});
}
});
+19 -4
View File
@@ -1,4 +1,19 @@
/*
* Widget styles
*/
@use "widgets/add/addform";
#add-item-panel {
max-width: 750px;
margin: auto;
.jenkins-choice-list__item {
// Default icons' text placeholder don't appear unless z-index is set
.jenkins-choice-list__item__icon {
span {
z-index: 1;
}
}
// Make 'Duplicate an existing item' input not full width
input[type="text"] {
width: 100%;
max-width: 400px;
}
}
}
-172
View File
@@ -1,172 +0,0 @@
@use "../../../scss/abstracts/mixins";
#add-item-panel {
max-width: 850px;
margin: auto;
form {
display: flex;
flex-direction: column;
gap: var(--section-padding);
#bottom-sticker {
margin-top: calc(var(--section-padding) * -1);
}
}
.item-copy {
position: relative;
.add-item-copy {
position: relative;
input[type="radio"] {
display: none;
}
label {
line-height: 48px;
padding: 0 10px 0 0;
}
input {
font-size: inherit;
}
}
}
.add-item-name,
.add-item-copy {
.input-validation-message {
color: var(--error-color);
padding-top: 2px;
font-weight: var(--font-bold-weight);
}
.input-message-disabled {
display: none;
}
}
.icon {
display: flex;
align-items: center;
justify-content: center;
height: 2.5rem;
width: 2.5rem;
img {
position: relative;
width: 100%;
height: 100%;
}
}
.default-icon {
height: 40px;
width: 40px;
border: 1px solid var(--text-color);
border-radius: 50%;
text-align: center;
line-height: 36px;
font-weight: bold;
font-size: 125%;
color: var(--text-color);
opacity: 0.75;
.b {
text-transform: lowercase;
}
}
input {
width: 50%;
min-width: 300px;
}
.categories {
.category {
.header {
padding: 0 20px;
h2 {
margin: 0;
padding: 15px 0 0;
}
p {
margin-top: 5px;
}
}
.desc {
color: var(--text-color-secondary);
}
}
}
.categories.flat {
.category {
border-bottom: none;
.header {
display: none;
}
}
}
}
.j-item-options li,
.j-item-options {
display: block;
margin: 0;
padding: 0;
list-style: none;
position: relative;
}
.j-item-options {
display: flex;
flex-direction: column;
gap: 5px;
li.active {
&::before {
background: var(--item-background--hover);
}
}
li {
@include mixins.item($border: false);
-webkit-touch-callout: none;
user-select: none;
padding: 0.75rem 1rem;
min-height: 68px;
display: grid;
grid-template-columns: auto 1fr;
gap: 0.25rem 1rem;
.icon {
grid-row: span 2;
}
label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-bold-weight);
color: var(--text-color);
cursor: pointer;
input {
position: absolute;
top: 24px;
left: 25px;
display: none;
}
}
.desc {
grid-column: 2;
}
}
}
+227
View File
@@ -0,0 +1,227 @@
.jenkins-choice-list {
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
border-radius: var(--form-input-border-radius);
border: var(--card-border-width) solid var(--card-border-color);
background: var(--card-background);
&__heading {
font-size: 1rem;
padding: 0.875rem;
border-bottom: var(--card-border-width) solid var(--card-border-color);
& > * {
margin: 0;
}
h2 {
font-weight: var(--font-bold-weight);
font-size: var(--font-size-sm);
margin-bottom: 0.5rem;
}
p {
color: var(--text-color-secondary);
}
}
&__item {
-webkit-touch-callout: none;
position: relative;
user-select: none;
display: flex;
align-items: stretch;
flex-direction: column;
white-space: unset;
border-radius: 0;
cursor: pointer;
&:not(:last-of-type) {
border-bottom: var(--card-border-width) solid var(--card-border-color);
}
&::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
transition: var(--standard-transition);
background: transparent;
box-shadow:
0 0 0 0.09375rem transparent,
0 0 0 0.5rem transparent;
pointer-events: none;
}
&:hover {
&::before {
background: color-mix(
in sRGB,
var(--text-color-secondary) 7.5%,
transparent
);
}
}
&:active,
&:has(input[type="radio"]:focus-visible) {
z-index: 10;
border-radius: calc(var(--form-input-border-radius) / 2);
&::before {
background: color-mix(
in sRGB,
var(--text-color-secondary) 12.5%,
transparent
) !important;
box-shadow:
0 0 0 0.09375rem
color-mix(
in sRGB,
var(--text-color-secondary) 20%,
var(--card-background)
),
0 0 0 0.34375rem
color-mix(
in sRGB,
color-mix(
in sRGB,
var(--text-color-secondary) 15%,
var(--card-background)
)
65%,
transparent
);
}
}
&:hover,
&:active,
&:has(input[type="radio"]:focus-visible) {
.jenkins-choice-list__item__icon::before {
background: rgb(from var(--text-color-secondary) r g b / 0.1);
box-shadow:
inset var(--jenkins-border--subtle-shadow),
inset 0 0 0 var(--jenkins-border-width)
rgb(from var(--text-color-secondary) r g b / 0.1);
}
}
&:has(input[type="radio"]:focus-visible) {
&::before {
box-shadow: 0 0 0 0.2rem var(--text-color) !important;
}
}
&:has(input:checked) {
&::before {
background: color-mix(
in sRGB,
var(--text-color-secondary) 5%,
transparent
);
}
.jenkins-choice-list__item__icon {
color: var(--background);
&::before {
box-shadow:
inset var(--jenkins-border--subtle-shadow),
inset 0 0 18px 18px var(--accent-color) !important;
}
}
.jenkins-choice-list__item__conditional {
display: block;
}
}
&:first-of-type {
border-top-left-radius: calc(
var(--form-input-border-radius) - var(--jenkins-border-width)
) !important;
border-top-right-radius: calc(
var(--form-input-border-radius) - var(--jenkins-border-width)
) !important;
}
&:last-of-type {
border-bottom-left-radius: calc(
var(--form-input-border-radius) - var(--jenkins-border-width)
) !important;
border-bottom-right-radius: calc(
var(--form-input-border-radius) - var(--jenkins-border-width)
) !important;
}
label {
display: grid;
gap: 0.25rem 0.875rem;
padding: 0.875rem;
grid-template-columns: auto 1fr;
cursor: pointer;
}
label > input[type="radio"] {
position: absolute;
opacity: 0;
}
&__icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 2.25rem;
width: 2.25rem;
grid-row: span 2;
z-index: 10;
transition: var(--standard-transition);
&::before {
content: "";
position: absolute;
inset: 0;
border-radius: var(--form-input-border-radius);
transition: var(--standard-transition);
z-index: 0;
background: rgb(from var(--text-color-secondary) r g b / 0.05);
box-shadow:
inset 0 0 0 var(--jenkins-border-width) var(--jenkins-border-color),
inset 0 0 0 var(--jenkins-border-width)
rgb(from var(--text-color-secondary) r g b / 0.05);
}
svg,
img {
position: relative;
width: 1.25rem;
height: 1.25rem;
z-index: 1;
}
}
&__label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-bold-weight);
color: var(--text-color);
}
&__description {
color: var(--text-color-secondary);
grid-column: 2;
padding-right: 2rem;
}
&__conditional {
display: none;
margin-left: 4rem;
margin-top: -0.25rem;
margin-bottom: 0.875rem;
}
}
}
+1
View File
@@ -5,6 +5,7 @@
@use "breadcrumbs";
@use "buttons";
@use "cards";
@use "choice-list";
@use "command-palette";
@use "content-blocks";
@use "description";
+1
View File
@@ -7,6 +7,7 @@
width: 100%;
min-height: 2.375rem;
box-shadow: var(--form-input-glow);
caret-color: var(--focus-input-border);
// Set height transition to 0s as vertical resizing has a delay/lag otherwise
transition:
+16
View File
@@ -50,6 +50,22 @@
&--medium {
max-width: var(--form-item-max-width--medium);
}
&:has(> .validation-error-area .warning) {
--input-border: var(--warning-color);
--input-border-hover: var(--warning-color);
--focus-input-border: var(--warning-color);
--form-input-glow--focus: 0 0 0 0.25rem
color-mix(in sRGB, var(--warning-color) 20%, transparent);
}
&:has(> .validation-error-area .error) {
--input-border: var(--error-color);
--input-border-hover: var(--error-color);
--focus-input-border: var(--error-color);
--form-input-glow--focus: 0 0 0 0.25rem
color-mix(in sRGB, var(--error-color) 20%, transparent);
}
}
.jenkins-form-label {
@@ -88,7 +88,7 @@ class MyViewTest {
form.getInputByName("name").setValue("job");
form.getInputByName("name").blur();
// Select the item clicking on the first item type shown
HtmlElement itemType = newItemPage.getFirstByXPath("//div[@class='category']/ul/li");
HtmlElement itemType = newItemPage.querySelector(".jenkins-choice-list div label");
itemType.click();
rule.submit(form);
Item item = rule.jenkins.getItem("job");
@@ -844,7 +844,8 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object result = page.executeJavaScript("Array.from(document.querySelectorAll('.label')).filter(el => el.innerText.indexOf('" + customizableTLID.customDisplayName + "') !== -1)[0].parentElement.parentElement").getJavaScriptResult();
Object result = page.executeJavaScript("Array.from(document.querySelectorAll('.jenkins-choice-list__item__label'))" +
".filter(el => el.innerText.indexOf('" + customizableTLID.customDisplayName + "') !== -1)[0].parentElement.parentElement").getJavaScriptResult();
assertThat(result, instanceOf(HTMLElement.class));
HTMLElement resultElement = (HTMLElement) result;
assertThat(resultElement.getAttribute("onclick"), nullValue());
@@ -861,7 +862,7 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object result = page.executeJavaScript("document.querySelector('.xss-dn .label').innerHTML").getJavaScriptResult();
Object result = page.executeJavaScript("document.querySelector('.xss-dn .jenkins-choice-list__item__label').innerHTML").getJavaScriptResult();
assertThat(result, instanceOf(String.class));
String resultString = (String) result;
assertThat(resultString, not(containsString("<")));
@@ -877,7 +878,7 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object result = page.executeJavaScript("document.querySelector('.html-desc .desc strong')").getJavaScriptResult();
Object result = page.executeJavaScript("document.querySelector('.html-desc .jenkins-choice-list__item__description strong')").getJavaScriptResult();
assertThat(result, instanceOf(HTMLElement.class));
assertThat(((HTMLElement) result).getTagName(), is("STRONG"));
}
@@ -894,12 +895,12 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object resultIconChildrenCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon').children.length").getJavaScriptResult();
Object resultIconChildrenCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon').children.length").getJavaScriptResult();
assertThat(resultIconChildrenCount, instanceOf(Integer.class));
int resultIconChildrenCountInt = (int) resultIconChildrenCount;
assertEquals(1, resultIconChildrenCountInt);
Object resultImgAttributesCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon img').attributes.length").getJavaScriptResult();
Object resultImgAttributesCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon img').attributes.length").getJavaScriptResult();
assertThat(resultImgAttributesCount, instanceOf(Integer.class));
int resultImgAttributesCountInt = (int) resultImgAttributesCount;
assertEquals(1, resultImgAttributesCountInt);
@@ -929,7 +930,7 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object resultSrc = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon img').src").getJavaScriptResult();
Object resultSrc = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon img').src").getJavaScriptResult();
assertThat(resultSrc, instanceOf(String.class));
String resultSrcString = (String) resultSrc;
@@ -944,7 +945,7 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object result = page.executeJavaScript("document.querySelector('.hudson_model_FreeStyleProject .icon svg')").getJavaScriptResult();
Object result = page.executeJavaScript("document.querySelector('.hudson_model_FreeStyleProject .jenkins-choice-list__item__icon svg')").getJavaScriptResult();
assertThat(result, instanceOf(SVGElement.class));
SVGElement svg = (SVGElement) result;
assertThat(svg.getClassName_js(), is("icon-xlg"));
@@ -962,7 +963,7 @@ class ViewTest {
HtmlPage page = wc.goTo("view/all/newJob");
Object result = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .default-icon')").getJavaScriptResult();
Object result = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon')").getJavaScriptResult();
assertThat(result, instanceOf(HTMLElement.class));
HTMLElement resultHtml = (HTMLElement) result;
HTMLElement spanA = (HTMLElement) resultHtml.getFirstElementChild();
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><rect x="128" y="128" width="336" height="336" rx="57" ry="57" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M383.5 128l.5-24a56.16 56.16 0 00-56-56H112a64.19 64.19 0 00-64 64v216a56.16 56.16 0 0056 56h24M296 216v160M376 296H216" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>

After

Width:  |  Height:  |  Size: 454 B