diff --git a/CHANGELOG.md b/CHANGELOG.md
index f389d803..0372bc24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
+## [Unreleased]
+### Added
+* [#558](https://github.com/shlinkio/shlink-web-client/pull/558) Added dark text for tags where the generated background is too light, improving its legibility.
+
+### Changed
+* *Nothing*
+
+### Deprecated
+* *Nothing*
+
+### Removed
+* *Nothing*
+
+### Fixed
+* *Nothing*
+
+
## [3.5.1] - 2022-01-08
### Added
* *Nothing*
diff --git a/test/tags/helpers/Tag.test.tsx b/test/tags/helpers/Tag.test.tsx
new file mode 100644
index 00000000..44b68bba
--- /dev/null
+++ b/test/tags/helpers/Tag.test.tsx
@@ -0,0 +1,85 @@
+import { Mock } from 'ts-mockery';
+import { shallow, ShallowWrapper } from 'enzyme';
+import { ReactNode } from 'react';
+import ColorGenerator from '../../../src/utils/services/ColorGenerator';
+import { MAIN_COLOR } from '../../../src/utils/theme';
+import Tag from '../../../src/tags/helpers/Tag';
+
+describe('', () => {
+ const onClick = jest.fn();
+ const onClose = jest.fn();
+ const isColorLightForKey = jest.fn(() => false);
+ const getColorForKey = jest.fn(() => MAIN_COLOR);
+ const colorGenerator = Mock.of({ getColorForKey, isColorLightForKey });
+ let wrapper: ShallowWrapper;
+ const createWrapper = (text: string, clearable?: boolean, children?: ReactNode) => {
+ wrapper = shallow(
+
+ {children}
+ ,
+ );
+
+ return wrapper;
+ };
+
+ afterEach(jest.clearAllMocks);
+ afterEach(() => wrapper?.unmount());
+
+ it.each([
+ [ true ],
+ [ false ],
+ ])('includes an extra class when the color is light', (isLight) => {
+ isColorLightForKey.mockReturnValue(isLight);
+
+ const wrapper = createWrapper('foo');
+
+ expect((wrapper.prop('className') as string).includes('tag--light-bg')).toEqual(isLight);
+ });
+
+ it.each([
+ [ MAIN_COLOR ],
+ [ '#8A661C' ],
+ [ '#F7BE05' ],
+ [ '#5A02D8' ],
+ [ '#202786' ],
+ ])('includes generated color as backgroundColor', (generatedColor) => {
+ getColorForKey.mockReturnValue(generatedColor);
+
+ const wrapper = createWrapper('foo');
+
+ expect((wrapper.prop('style') as any).backgroundColor).toEqual(generatedColor);
+ });
+
+ it('invokes expected callbacks when appropriate events are triggered', () => {
+ const wrapper = createWrapper('foo', true);
+
+ expect(onClick).not.toBeCalled();
+ expect(onClose).not.toBeCalled();
+
+ wrapper.simulate('click');
+ expect(onClick).toHaveBeenCalledTimes(1);
+
+ wrapper.find('.tag__close-selected-tag').simulate('click');
+ expect(onClose).toHaveBeenCalledTimes(1);
+ });
+
+ it.each([
+ [ true, 1, 'auto' ],
+ [ false, 0, 'pointer' ],
+ [ undefined, 0, 'pointer' ],
+ ])('includes a close component when the tag is clearable', (clearable, expectedCloseBtnAmount, expectedCursor) => {
+ const wrapper = createWrapper('foo', clearable);
+
+ expect(wrapper.find('.tag__close-selected-tag')).toHaveLength(expectedCloseBtnAmount);
+ expect((wrapper.prop('style') as any).cursor).toEqual(expectedCursor);
+ });
+
+ it.each([
+ [ undefined, 'foo' ],
+ [ 'bar', 'bar' ],
+ ])('falls back to text as children when no children are provided', (children, expectedChildren) => {
+ const wrapper = createWrapper('foo', false, children);
+
+ expect(wrapper.html()).toContain(`>${expectedChildren}`);
+ });
+});
diff --git a/test/utils/services/ColorGenerator.test.ts b/test/utils/services/ColorGenerator.test.ts
index b2d435ce..2836f8e6 100644
--- a/test/utils/services/ColorGenerator.test.ts
+++ b/test/utils/services/ColorGenerator.test.ts
@@ -1,6 +1,7 @@
import { Mock } from 'ts-mockery';
import ColorGenerator from '../../../src/utils/services/ColorGenerator';
import LocalStorage from '../../../src/utils/services/LocalStorage';
+import { MAIN_COLOR } from '../../../src/utils/theme';
describe('ColorGenerator', () => {
let colorGenerator: ColorGenerator;
@@ -47,7 +48,7 @@ describe('ColorGenerator', () => {
describe('isColorLightForKey', () => {
it.each([
- [ '#4696e5', true ], // Shlink brand color
+ [ MAIN_COLOR, true ],
[ '#8A661C', false ],
[ '#F7BE05', true ],
[ '#5A02D8', false ],