CSS specificity testing - css

Are there any good tools or methods for automatic testing css selectors?
I'm developing a SCSS framework and would like to include automated tests in it.
Specifically I would like to have tests to ensure that the css selectors are working properly.
Say for instance that I have the html:
<input class="btn" disabled id="test"></input>
and css
.btn {
color: red;
...
}
.btn:disabled {
color: green;
...
}
I would like to have a test that ensures that the element above with id=test, have the .btn:disabled as the css class with highest priority (last class with highest specificity) and .btn as the second highest priority. In other words, I would like to ensure that the .btn:disabled and .btn css style is applied on the element and that styles in .btn:disabled are overwriting the styles in .btn.
I'm thinking of doing this in selenium. Are there any good ways of doing this? I would not like to hard code the css values into the tests.

The method I settled with is to use getComputedStyle to get the style with "highest priority". In the css I add a "tag" to the content property. In jasmine I then check if the desired tag is the computedStyle. (I will extend this in scss so that the content property is set by a mixin if test mode is used and not set in production.) This only makes a unit test for the class of highest priority, but not for the second highest etc.
Below is a tests to illustrate the example (only the first and last should pass).
// specs code
describe("CSS", function() {
it("Div element of class test should be handled by .test", () => {
const testdiv = document.getElementById("testdiv")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('".test"');
});
it("Div element of class test should be handled by div", () => {
const testdiv = document.getElementById("testdiv")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('"div"');
});
it("Div element should be handled by .test", () => {
const testdiv = document.getElementById("testdiv2")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('".test"');
});
it("Div element of class test should be handled by div", () => {
const testdiv = document.getElementById("testdiv2")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('"div"');
});
});
// load jasmine htmlReporter
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
.test {
content: '.test';
}
div {
content: 'div';
}
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet"/>
<div class="test" id="testdiv">TestDiv</div>
<div id="testdiv2">TestDiv</div>

If I understand the question correctly, you're basically asking for a test (for your example) like this:
if -> Element.ComputedStyle.color = green
then -> test passed
else -> test failed (you have CSS structure errors)
Obviously, the browser will interpret specificity correctly. So you're really testing if changes you made by adding/overriding CSS caused unintended visual consequences.
That seems pretty manual question to answer since you'll have to decide what each of those correct states is and then maintain the tests. If you go this route, I'd look at something like Backstop.js. Though CSS visual regression testing is REALLY complex so be careful how much you expect from it.
The Manual Way
Could you solve the problem somewhat manually by creating a SCSS variable that's usually transparent? Then as you're adding/changing code add that variable and change the color to something like pink that's really obvious? At this point, you should see where things override when you render the page.
If you're making a CSS framework, I'd test against your documentation since that should show you previous examples that would be overridden.
CSS Blocks
You may also want to look into the CSS Blocks API. It's not going to be a "test" exactly but the API provides scoping and compile errors that might help you catch some of those issues sooner than later.
Here's the pertinent part:
With CSS Blocks new resolution system, cascade conflicts will be caught for you before you even know they exist and you will never have to fight a specificity war ever again.

As you have mentioned you can achieve this with Selenium. In terms of methodology, if you're looking to maintain this long term then I would recommend following the Page Object Model. The official documentation on this is available here, and there are some other articles in various language here, here, and here.
Essentially what it boils down to is create classes or models for the pages (or page sections/components (as in a form that has multiple controls)), this class will then have properties/fields for each of the elements on the page that you want to interact with. The advantages of this approach are:
A single place to change if you need to update a selector (maintainability)
The underlaying code which can be ugly can be exposed through a nice interface that uses fluent syntax (readability)
How this looks (since you haven't specified a language I'll go with C#:
public class LoginPage
{
// FindBy attributes should decorate each property to specify the selector
public WebElement UsernameInput { get; set; }
public WebElement PasswordInput { get; set; }
public WebElement LoginButton { get; set; }
public LoginPage()
{
...
}
public LoginPage Load(this LoginPage page)
{
// code to navigate to the login page
}
public LoginPage EnterCredentials(this LoginPage page, string username, string password)
{
// code to fill out inputs
}
public HomePage Login(this LoginPage page)
{
// code to click login button
}
// Other methods
}
How this looks when you use it:
HomePage homePage =
new LoginPage()
.Load()
.EnterCredentials("user", "pass")
.Login();
// Now you can perform operations on the HomePage

CSS Specificity
As per the documentation Specificity is the logic by which the browser decides which CSS property values are the most relevant to an element incase there are two or more conflicting CSS rules that point to the same element and which will be applied. Specificity is calculated based on the matching rules which are composed as per different CSS selectors.
How to calculate Specificity
There are a couple of rules to calculate Specificity based on points which are as follows:
style attribute: 1000
id attribute: 100
class or pseudo-class: 1
Calculating the CSS Specificity
Let us calculate the Specificity of both the CSS
Sample A:
.btn {
color: red;
}
Explanation: Contains a class i.e. btn. So Specificity is 1.
Sample B:
.btn:disabled {
color: green;
...
}
Explanation: Contains a class i.e. btn and a pseudo-class i.e. disabled. So Specificity is 2.
Tests
The CSS specificity can also be visually verified through Specificity Calculator:
Conclusion
As the CSS sample B has a greater Specificity, so CSS sample B will be applied to the element:
<input class="btn" disabled id="test"></input>
Outro
However there are some more granular CSS Specificity Rules on:
Equal specificity: the latest rule counts.
ID selectors have a higher specificity than attribute selectors.
Contextual selectors are more specific than a single element selector.
A class selector beats any number of element selectors.
You can find a detailed documentation in CSS Specificity

Related

How to declare SCSS <length> type attribute selector by REACT property

Basically I want to create a reusable component which can be slightly modified from the outside in react:
SomewhereElse.tsx
...
<FooComponent width={200}>
...
FooComponent.tsx
interface IFooProps {
//default width of 150px
width?: 150;
//default margin of 5px
margin?: 5;
}
interface IFooState{
checked: boolean;
}
class FooComponent extends Component<IFooProps, IFooState> {
constructor(props: IFooProps) {
super(props);
...
}
...
render(): ReactElement {
return (
<div className="foo-component"
data-width={this.props.width + 'px'}
>
...
FooComponent.scss
.foo-component {
...
width: attr(data-width length);
...
But it always gives me the following error when running the application (chrome):
... Please consider that I need a "number" as property because I am doing some calculations based on that number for some of the inner components so everything fits together.
EDIT:
Using "style" is not working for me because I have some ::before ::after features in my scss which are broken when using style because they occasionally modify "right:" based on the width.
For better understanding, this is my base:
https://www.sitepoint.com/react-toggle-switch-reusable-component/
To answer your question
Please find a solution with attribute selector or tell me that it is not possible because of X reasons (link to docs in best-case).
No, although it would be super useful, according to my research it's currently not possible:
MDN about CSS attr():
Note: The attr() function can be used with any CSS property, but support for properties other than content is experimental, and support for the type-or-unit parameter is sparse.
According to caniuse.com about css3-attr, the ability to use attr() on any CSS property (besides content, and to use it for non-string values (e.g. numbers, colors) via <type_or_unit> as defined per "CSS Values and Units Level 3" is currently unsupported by all browsers.
See also the statement on the current draft for "CSS Values and Units Module Level 4":
The following features are at-risk, and may be dropped during the CR period: toggle(), attr() [...]
(Source: https://drafts.csswg.org/css-values/#status)
In the Chromium issue, I found a link to an interesting resource I wasn't aware of: web-platform-tests dashboard for css-values. Take a look at the (failing) tests prefixed with attr-, especially attr-length-valid.html and attr-px-valid.html.
But there is hope: the Chromium team recently (05/15/2020) posted an Intent to Implement and Ship: CSS advanced attr() function
Implement the augmentation to attr() specified in CSS Level 4, namely, allowing various types (besides ) and usage in all CSS properties (besides pseudo-element 'content').
Note: CSS Level 4 has made substantial revisions to attr() compared to Level 3 to ease the implementation. We'll follow CSS4. [...]
Motivation: This is a highly requested feature, with 77 stars at crbug.com/246571. [...]
Chromium tracking Issue 246571: Implement CSS3 attribute / attr references
)
Firefox also show recent activity, see Firefox tracking Bug 435426 [meta] Implement CSS Values 3 extensions to attr(), there they at least are referring to Chromiums current efforts.
(WebKit Bug 26609 - Support CSS3 attr() function shows no activity, which could be a deal-breaker)
As suggested from G-Cyrillus I have found a possible solution using CSS custom properties. Unfortunetly I did not find a solution using attribute selector so I could stick to one solution type.
Add your custom properties in the scss file "parent" class like:
.foo-component {
--customwidth: 150px;
--custommargin: 5px;
width: var(--customwidth, 150px);
...
&-inner {
margin-left: var(--custommargin, 5px);
}
...
}
After you declared the customproperty it's possible to use it with var(<propertyname>, <initialvalue>).
In React you slightly can modify the render() like:
render(): ReactElement {
//set the custom properties for width, margin and slider position
let cssProperties = {};
cssProperties['--customwidth'] = this.props.width == null ? '150px' : this.props.width + 'px';
cssProperties['--custommargin'] = this.props.margin == null ? '150px' : this.props.margin + 'px';
cssProperties['--switchposition'] = (this.props.width == null ? 115 : this.props.width - 35) + 'px';
return (
<div className="foo-component" style={cssProperties}>
...
}
Which will work for every component seperatly.
An easier way would be using inline style:
return (
<div className="foo-component"
style=`width:${this.props.width};`
>
With attr you need to specify the attribute by providing the selector in your scss styles
.foo-component[data-width] {
...
width: attr(data-width length);
...
If you have multiple such attribute you can write them like
.foo-component[data-width][data-margin] {
...
width: attr(data-width length);
margin: attr(data-margin number);
...
Please check the MDN docs for more details

Dynamically switch global CSS for Angular8 app (client branding)

I want to dynamically switch Angulars global CSS files based on which client is connecting. This will be used for client-branding purposes, including fonts, colors, photos, headers, footers, button-styles, etc.
Each client has provided us with a CSS file, which we need to integrate into our app. We have hundreds of clients.
Current solution is to try and override the CSS of individual components at load. This is bad because it adds a lot of boilerplate:
Html:
<link id="theme" rel="stylesheet" href="./assets/stylesheets/{{cclientCode}}.css">
ts:
ngOnInit() {
this.service.clientCode.subscribe(clientCode => this.clientCode = clientCode);
}
My workaround isn't working because the link html is called before the {{}} has a chance to load in the value.
I'm also not motivated to fix my workaround because its just that -a workaround. Instead, I want to implement something that works globally, without any per-component boilerplate.
What I want is the ability to dynamically switch the global Angular style for each client. So something like:
"styles": [
"src/assets/stylesheets/angular_style.css",
"src/assets/stylesheets/client_style.css"
]
Where client_style.css is served differently to each client.
I've found a solution that I think is workable. It definitely has issues though, so if anyone has their own answer, please still share!
First, I added a clientCode String field to SessionDataService, a global service I use to move component-agnostic data around my app:
export class SessionDataService {
clientCode: BehaviorSubject<String>;
constructor(){
this.clientCode = new BehaviorSubject('client_default');
}
setClientCode(value: String) {
this.clientCode.next(value);
}
}
Then, inside app.component.ts, I added a BehaviorSubject listener to bring in the value of clientCode dynamically:
public clientCode: String;
constructor(private service : SessionDataService) {
this.service.clientCode.subscribe(clientCode => this.clientCode = clientCode);
}
Next, I added a wrapper around my entire app.component.html:
<div [ngClass]="clientCode">
--> ALL app components go here (including <router-outlet>)
</div>
So at this point, I've created a system that dynamically adds client-code CSS classes to my components, including all children :)
Finally, I just have to write CSS rules:
.ClientOne p {
color: red;
}
.ClientOne .btn {
background-color: red;
}
.ClientTwo.dashboard {
height: 15%;
}
I hope this helps somebody! Essentially the "trick" here is to add a ngClass that wraps the entire app, and then justify all client-specific CSS rules with their client code.

How to set style for an element in typescript?(Angular)

How can I set the background colour for an item within an if statement in typescript? I used querySelector but the answer can use anything to achieve the result.
The selector is (.mat-step:nth-child(2) .mat-step-header .mat-step-icon-selected).
Here is the code in a stackblitz.
I would appreciate any help!
The stackblitz example can be helpful but there is a lot in there to summarise what you are askign for, this answer is a generic way of doing so, meaning you can apply it to your code as and where you see fit.
Declare you boolean.
public value = true;
Now declare the CSS class you would like to use.
.exmaple-class {
background: red;
}
Then on the selected HTML element you want to apply the class.
<div [class.example-class]="value === true"></div>
or just
<div [class.example-class]="value"></div>
As this still equates to true. If value were set to false then the class would not be applied.
If you want to start building more classes and options for a specific element you can look into Angular's ngStyle.
Add in this, think this is what you are also asking for, little different. It only runs after the view is loaded, not working in you example because the HTML has not yet been drawn.
public ngAfterViewInit(): void
{
this.changeColour();
}
public changeColour() {
document.querySelector<HTMLInputElement>(".mat-step-icon-selected").style.backgroundColor = 'red';
}
}
Then add a click event to ensure that each time you select something the selector is updated.
<div class="center-contrainer" (click)=changeColour()>

How to dynamically generate CSS class and/or set its property

The title is not really a question it is more like an idea, I don't know what approach is best for my situation.
So, the problem. I have some 3rd party component that have some complex structure and styling. Some part of it has some predefined CSS class that I can override with CSS in my surrounding component. Something like this:
my component:
<div class="my-cmp-container">
<some-3rd-party-cmp></some-3rd-party-cmp>
</div>
3rd party component:
<div class="3rd-party-css-class">
...
</div>
For example, 3rd-party-css-class has style background-color: #f00, I can override it with .my-cmp-container .3rd-party-css-class { background-color: #fff; } etc. But. What if I need to set color dynamically, it's stored in a DB for example and I can't predefine each case in my class' CSS. I just have the color in hex.
In theory I can generate unique string to set as CSS class for every instance of some-3rd-party-cmp and somehow generate CSS in my component? I'm lost a little, what is the best approach for this?
Edit: Code sample to illustrate the situation https://stackblitz.com/edit/angular-kxdatq
What you are trying to do is the subject of this open issue about stylesheet binding in Angular. Until that feature is available, you can get what you want with a custom directive. Here is a directive that retrieves the checkbox element generated by ng-zorro-antd and applies two color attributes to it. The two colors are #Input properties and the directive implements OnChanges which allows to react to property binding changes.
#Directive({
selector: "[nz-checkbox][nz-chk-style]"
})
export class CheckBoxStyleDirective implements OnInit, OnChanges {
#Input("nz-chk-bkgnd") chkBkgndColor: string;
#Input("nz-chk-border") chkBorderColor: string;
private checkbox: HTMLElement;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnInit() {
this.checkbox = this.el.nativeElement.querySelector(".ant-checkbox-inner");
this.updateBackgroundColor();
this.updateBorderColor();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.chkBkgndColor) {
this.updateBackgroundColor();
}
if (changes.chkBorderColor) {
this.updateBorderColor();
}
}
updateBackgroundColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "background-color", this.chkBkgndColor);
}
}
updateBorderColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "border-color", this.chkBorderColor);
}
}
}
Once the directive attribute selector nz-chk-style is applied to the 3rd party element, you can set the checkbox background and border colors with property binding as follows:
<span nz-checkbox nz-chk-style [nz-chk-bkgnd]="bkgndColor" [nz-chk-border]="borderColor" >
See this interactive stackblitz for a demo.
Not sure if you are using Angular but you tagged it, so I guess you are.
If you want to change only the color and nothing more, instead of having a .3rd-party-css-class class, you could just have your with an ng-style like so:
<some-3rd-party-cmp ng-style="{ color: your_color_hex_variable }"></some-3rd-party-cmp>
You can also define a whole object if styles and pass it.
You can also use ng-class and pass one or an array of class names what you want to put additionally on your component:
<some-3rd-party-cmp ng-class="[cls1, cls2, cls3]"></some-3rd-party-cmp>
<some-3rd-party-cmp ng-class="[3rd-party-css-class, someCondition ? 'another-class-name' : '']"></some-3rd-party-cmp>
In the classes you can define the css rules you want to apply and thats it.
With this solutions you can avoid having extra wrapper elements for styling purposes which is a nice thing.

HIghlighting row during runtime

I am trying to highlight a row based user input. I am using Angular 5, with ag-grid-angular 19.1.2. Setting the style with gridOptions.getRowStyle changes the background, but I would rather use scss classes if possible. The function setHighlight() is called in the html file through (change)=setHighlight()
setHighlight() {
const nextChronoId = this.getNextChronoDateId();
// this.highlightWithStyle(nextChronoId); // Working solution
this.highlightWithClass(nextChronoId);
const row = this.gridApi.getDisplayedRowAtIndex(nextChronoId);
this.gridApi.redrawRows({ rowNodes: [row]})
}
Function definitions:
highlightWithStyle(id: number) {
this.gridApi.gridCore.gridOptions.getRowStyle = function(params) {
if (params.data.Id === id) {
return { background: 'green' }
}
}
}
highlightWithClass(id: number) {
this.gridApi.gridCore.gridOptions.getRowClass = function(params) {
if (params.data.Id === id) {
return 'highlighted'
}
}
}
My scss class:
/deep/ .ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last, .highlighted{
background-color: green;
}
My issue
Using getRowClass does not apply my highlighted class correctly to the rowNode. After reading (and trying) this, I think that my custom scss class overwritten by the ag-classes. The same problem occurs when using rowClassRules.
Question
How can I make Angular 5 and ag-grid work together in setting my custom scss class correctly?
Stepping with the debugger shows the class is picked up and appended to the native ag-grid classes.
In rowComp.js:
Addition, screen dump from dev tools:
angular's ViewEncapsulationis the culprit here.
First be aware that all shadow piercing selectors like /deep/ or ::ng-deep are or will be deprecated.
this leaves, to my knowledge, two options.
use ViewEncapsulation.None
add your highlighted class to the global stylesheet
setting ViewEncapsulation.None brings its own possible problems:
All components styles would become globally available styles.
I would advise to go with option two.
this answers sums it up pretty well.
Additionally:
.ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last
this selector will never match anything, you should change it to
.ag-theme-balham .ag-row.ag-row-no-focus.ag-row-even.ag-row-level0.ag-row-last
every class after ag-theme-balham exists on the same element.
with the selector you wrote, you would denote a hierarchy.
Hope this helps

Resources