refactor(color-strip): rename static -> single + frontend follow-through
The "static" source kind always rendered a SINGLE color and the name confused new code paths. Rename the module + kind to "single". Storage keeps backward-compatible serialisation. Frontend color-strip cards / gradient / index / test modules and the affected tests follow the new name.
This commit is contained in:
@@ -9,12 +9,12 @@ from .base import ColorStripStream, _SimpleNoise1D, _gradient_noise
|
||||
from .gradient import GradientColorStripStream
|
||||
from .helpers import _compute_gradient_colors
|
||||
from .picture import PictureColorStripStream
|
||||
from .static import StaticColorStripStream
|
||||
from .single import SingleColorStripStream
|
||||
|
||||
__all__ = [
|
||||
"ColorStripStream",
|
||||
"PictureColorStripStream",
|
||||
"StaticColorStripStream",
|
||||
"SingleColorStripStream",
|
||||
"GradientColorStripStream",
|
||||
"_compute_gradient_colors",
|
||||
"_SimpleNoise1D",
|
||||
|
||||
+13
-13
@@ -1,4 +1,4 @@
|
||||
"""Static color strip stream — solid color with optional animation."""
|
||||
"""Single color strip stream — solid color with optional animation."""
|
||||
|
||||
import colorsys
|
||||
import math
|
||||
@@ -18,7 +18,7 @@ from .base import ColorStripStream
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class StaticColorStripStream(ColorStripStream):
|
||||
class SingleColorStripStream(ColorStripStream):
|
||||
"""Color strip stream that returns a constant single-color array.
|
||||
|
||||
When animation is enabled a 30 fps background thread updates _colors with
|
||||
@@ -28,7 +28,7 @@ class StaticColorStripStream(ColorStripStream):
|
||||
def __init__(self, source):
|
||||
"""
|
||||
Args:
|
||||
source: StaticColorStripSource config
|
||||
source: SingleColorStripSource config
|
||||
"""
|
||||
self._colors_lock = threading.Lock()
|
||||
self._running = False
|
||||
@@ -64,7 +64,7 @@ class StaticColorStripStream(ColorStripStream):
|
||||
if self._auto_size and device_led_count > 0 and device_led_count != self._led_count:
|
||||
self._led_count = device_led_count
|
||||
self._rebuild_colors()
|
||||
logger.debug(f"StaticColorStripStream auto-sized to {device_led_count} LEDs")
|
||||
logger.debug(f"SingleColorStripStream auto-sized to {device_led_count} LEDs")
|
||||
|
||||
@property
|
||||
def target_fps(self) -> int:
|
||||
@@ -98,36 +98,36 @@ class StaticColorStripStream(ColorStripStream):
|
||||
self._running = True
|
||||
self._thread = threading.Thread(
|
||||
target=self._animate_loop,
|
||||
name="css-static-animate",
|
||||
name="css-single-animate",
|
||||
daemon=True,
|
||||
)
|
||||
self._thread.start()
|
||||
logger.info(f"StaticColorStripStream started (leds={self._led_count})")
|
||||
logger.info(f"SingleColorStripStream started (leds={self._led_count})")
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running = False
|
||||
if self._thread:
|
||||
self._thread.join(timeout=5.0)
|
||||
if self._thread.is_alive():
|
||||
logger.warning("StaticColorStripStream animate thread did not terminate within 5s")
|
||||
logger.warning("SingleColorStripStream animate thread did not terminate within 5s")
|
||||
self._thread = None
|
||||
logger.info("StaticColorStripStream stopped")
|
||||
logger.info("SingleColorStripStream stopped")
|
||||
|
||||
def get_latest_colors(self) -> Optional[np.ndarray]:
|
||||
with self._colors_lock:
|
||||
return self._colors
|
||||
|
||||
def update_source(self, source) -> None:
|
||||
from ledgrab.storage.color_strip_source import StaticColorStripSource
|
||||
from ledgrab.storage.color_strip_source import SingleColorStripSource
|
||||
|
||||
if isinstance(source, StaticColorStripSource):
|
||||
if isinstance(source, SingleColorStripSource):
|
||||
prev_led_count = self._led_count if self._auto_size else None
|
||||
self._update_from_source(source)
|
||||
# If we were auto-sized, preserve the runtime LED count across updates
|
||||
if prev_led_count and self._auto_size:
|
||||
self._led_count = prev_led_count
|
||||
self._rebuild_colors()
|
||||
logger.info("StaticColorStripStream params updated in-place")
|
||||
logger.info("SingleColorStripStream params updated in-place")
|
||||
|
||||
def set_clock(self, clock) -> None:
|
||||
"""Set or clear the sync clock runtime. Thread-safe (read atomically by loop)."""
|
||||
@@ -266,7 +266,7 @@ class StaticColorStripStream(ColorStripStream):
|
||||
with self._colors_lock:
|
||||
self._colors = buf
|
||||
except Exception as e:
|
||||
logger.error(f"StaticColorStripStream animation error: {e}")
|
||||
logger.error(f"SingleColorStripStream animation error: {e}")
|
||||
|
||||
if (anim and anim.get("enabled")) or self._is_color_bound():
|
||||
sleep_target = frame_time
|
||||
@@ -274,6 +274,6 @@ class StaticColorStripStream(ColorStripStream):
|
||||
sleep_target = 0.25
|
||||
limiter.wait(sleep_target)
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal StaticColorStripStream loop error: {e}", exc_info=True)
|
||||
logger.error(f"Fatal SingleColorStripStream loop error: {e}", exc_info=True)
|
||||
finally:
|
||||
self._running = False
|
||||
@@ -9,7 +9,7 @@ from ledgrab.core.processing.color_strip import ( # noqa: F401
|
||||
ColorStripStream,
|
||||
GradientColorStripStream,
|
||||
PictureColorStripStream,
|
||||
StaticColorStripStream,
|
||||
SingleColorStripStream,
|
||||
_compute_gradient_colors,
|
||||
_gradient_noise,
|
||||
_SimpleNoise1D,
|
||||
@@ -18,7 +18,7 @@ from ledgrab.core.processing.color_strip import ( # noqa: F401
|
||||
__all__ = [
|
||||
"ColorStripStream",
|
||||
"PictureColorStripStream",
|
||||
"StaticColorStripStream",
|
||||
"SingleColorStripStream",
|
||||
"GradientColorStripStream",
|
||||
"_compute_gradient_colors",
|
||||
"_SimpleNoise1D",
|
||||
|
||||
@@ -38,7 +38,7 @@ registerIconEntityType('color_strip_source', makeSimpleIconAdapter<ColorStripSou
|
||||
typeLabelKey: 'device.icon.entity.color_strip_source',
|
||||
typeLabelFallback: 'Color strip',
|
||||
cardSelectors: (id) => [`[data-css-id="${CSS.escape(id)}"]`],
|
||||
bodyExtras: (rec) => ({ source_type: (rec as any)?.source_type ?? 'static' }),
|
||||
bodyExtras: (rec) => ({ source_type: (rec as any)?.source_type ?? 'single_color' }),
|
||||
}));
|
||||
|
||||
/* ── Types ────────────────────────────────────────────────────── */
|
||||
@@ -88,7 +88,7 @@ function _gradientEntityStripHTML(stops: Array<{ position: number; color: number
|
||||
/* ── Non-picture types set ────────────────────────────────────── */
|
||||
|
||||
const NON_PICTURE_TYPES = new Set([
|
||||
'static', 'gradient', 'effect', 'composite', 'mapped',
|
||||
'single_color', 'gradient', 'effect', 'composite', 'mapped',
|
||||
'audio', 'api_input', 'notification', 'daylight', 'candlelight', 'weather', 'processed', 'key_colors',
|
||||
'math_wave',
|
||||
]);
|
||||
@@ -96,8 +96,8 @@ const NON_PICTURE_TYPES = new Set([
|
||||
/* ── Per-type card property renderers ─────────────────────────── */
|
||||
|
||||
const CSS_CARD_RENDERERS: Record<string, CardPropsRenderer> = {
|
||||
static: (source, { clockBadge, animBadge }) => {
|
||||
const colorBadge = _bindableColorBadge(source.color, [255, 255, 255], t('color_strip.static_color'));
|
||||
single_color: (source, { clockBadge, animBadge }) => {
|
||||
const colorBadge = _bindableColorBadge(source.color, [255, 255, 255], t('color_strip.single_color'));
|
||||
return `
|
||||
${colorBadge}
|
||||
${animBadge}
|
||||
@@ -312,7 +312,7 @@ function _renderPictureCardProps(source: ColorStripSource, pictureSourceMap: Rec
|
||||
/* ── Main card builder ────────────────────────────────────────── */
|
||||
|
||||
const STRIP_BADGE: Record<string, string> = {
|
||||
static: 'STRIP · COLOR',
|
||||
single_color: 'STRIP · COLOR',
|
||||
gradient: 'STRIP · GRD',
|
||||
effect: 'STRIP · FX',
|
||||
composite: 'STRIP · COMP',
|
||||
@@ -336,7 +336,7 @@ export function createColorStripCard(source: ColorStripSource, pictureSourceMap:
|
||||
? `<span class="stream-card-prop stream-card-link" title="${t('color_strip.clock')}" onclick="event.stopPropagation(); navigateToCard('streams','sync','sync-clocks','data-id','${source.clock_id}')">${ICON_CLOCK} ${escapeHtml(clockObj.name)}</span>`
|
||||
: source.clock_id ? `<span class="stream-card-prop">${ICON_CLOCK} ${source.clock_id}</span>` : '';
|
||||
|
||||
const isAnimatable = source.source_type === 'static' || source.source_type === 'gradient';
|
||||
const isAnimatable = source.source_type === 'single_color' || source.source_type === 'gradient';
|
||||
const anim = isAnimatable && source.animation && source.animation.enabled ? source.animation : null;
|
||||
const animBadge = anim
|
||||
? `<span class="stream-card-prop" title="${t('color_strip.animation')}">${ICON_SPARKLES} ${t('color_strip.animation.type.' + anim.type) || anim.type}</span>`
|
||||
|
||||
@@ -194,6 +194,8 @@ export async function closeGradientEditor() {
|
||||
|
||||
export async function saveGradientEntity() {
|
||||
const id = (document.getElementById('gradient-editor-id') as HTMLInputElement).value;
|
||||
if (gradientEditorModal.closeIfPristine(id)) return;
|
||||
|
||||
const name = (document.getElementById('gradient-editor-name') as HTMLInputElement).value.trim();
|
||||
const description = (document.getElementById('gradient-editor-description') as HTMLInputElement).value.trim() || null;
|
||||
const tags = _gradientTagsInput ? _gradientTagsInput.getValue() : [];
|
||||
|
||||
@@ -127,7 +127,7 @@ class CSSEditorModal extends Modal {
|
||||
if (_candlelightWindWidget) { _candlelightWindWidget.destroy(); _candlelightWindWidget = null; }
|
||||
if (_weatherSpeedWidget) { _weatherSpeedWidget.destroy(); _weatherSpeedWidget = null; }
|
||||
if (_weatherTempInfluenceWidget) { _weatherTempInfluenceWidget.destroy(); _weatherTempInfluenceWidget = null; }
|
||||
if (_staticColorWidget) { _staticColorWidget.destroy(); _staticColorWidget = null; }
|
||||
if (_singleColorWidget) { _singleColorWidget.destroy(); _singleColorWidget = null; }
|
||||
if (_effectColorWidget) { _effectColorWidget.destroy(); _effectColorWidget = null; }
|
||||
if (_apiInputFallbackColorWidget) { _apiInputFallbackColorWidget.destroy(); _apiInputFallbackColorWidget = null; }
|
||||
if (_candlelightColorWidget) { _candlelightColorWidget.destroy(); _candlelightColorWidget = null; }
|
||||
@@ -150,7 +150,7 @@ class CSSEditorModal extends Modal {
|
||||
picture_source: (document.getElementById('css-editor-picture-source') as HTMLInputElement).value,
|
||||
interpolation: (document.getElementById('css-editor-interpolation') as HTMLInputElement).value,
|
||||
smoothing: _smoothingWidget ? JSON.stringify(_smoothingWidget.getValue()) : '0.3',
|
||||
color: _staticColorWidget ? JSON.stringify(_staticColorWidget.getValue()) : '[]',
|
||||
color: _singleColorWidget ? JSON.stringify(_singleColorWidget.getValue()) : '[]',
|
||||
led_count: (document.getElementById('css-editor-led-count') as HTMLInputElement).value,
|
||||
gradient_stops: type === 'gradient' ? JSON.stringify(getGradientStops()) : '[]',
|
||||
animation_type: (document.getElementById('css-editor-animation-type') as HTMLInputElement).value,
|
||||
@@ -223,7 +223,7 @@ let _candlelightWindWidget: BindableScalarWidget | null = null;
|
||||
let _weatherSpeedWidget: BindableScalarWidget | null = null;
|
||||
let _weatherTempInfluenceWidget: BindableScalarWidget | null = null;
|
||||
|
||||
let _staticColorWidget: BindableColorWidget | null = null;
|
||||
let _singleColorWidget: BindableColorWidget | null = null;
|
||||
let _effectColorWidget: BindableColorWidget | null = null;
|
||||
let _apiInputFallbackColorWidget: BindableColorWidget | null = null;
|
||||
let _candlelightColorWidget: BindableColorWidget | null = null;
|
||||
@@ -303,7 +303,7 @@ async function configureKCRegions(sourceId: string): Promise<void> {
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
const CSS_TYPE_KEYS = [
|
||||
'picture', 'picture_advanced', 'static', 'gradient',
|
||||
'picture', 'picture_advanced', 'single_color', 'gradient',
|
||||
'effect', 'composite', 'mapped', 'audio',
|
||||
'api_input', 'notification', 'daylight', 'candlelight', 'weather', 'processed', 'key_colors',
|
||||
'game_event', 'math_wave',
|
||||
@@ -341,7 +341,7 @@ function _ensureCSSTypeIconSelect() {
|
||||
const CSS_SECTION_MAP: Record<string, string> = {
|
||||
'picture': 'css-editor-picture-section',
|
||||
'picture_advanced': 'css-editor-picture-section',
|
||||
'static': 'css-editor-static-section',
|
||||
'single_color': 'css-editor-single-color-section',
|
||||
'gradient': 'css-editor-gradient-section',
|
||||
'effect': 'css-editor-effect-section',
|
||||
'composite': 'css-editor-composite-section',
|
||||
@@ -399,7 +399,7 @@ export function onCSSTypeChange() {
|
||||
|
||||
const animSection = document.getElementById('css-editor-animation-section') as HTMLElement;
|
||||
const animTypeSelect = document.getElementById('css-editor-animation-type') as HTMLSelectElement;
|
||||
if (type === 'static' || type === 'gradient') {
|
||||
if (type === 'single_color' || type === 'gradient') {
|
||||
animSection.style.display = '';
|
||||
const opts = type === 'gradient'
|
||||
? ['none','breathing','gradient_shift','wave','noise_perturb','hue_rotate','strobe','sparkle','pulse','candle','rainbow_fade']
|
||||
@@ -417,7 +417,7 @@ export function onCSSTypeChange() {
|
||||
(document.getElementById('css-editor-led-count-group') as HTMLElement).style.display =
|
||||
hasLedCount.includes(type) ? '' : 'none';
|
||||
|
||||
const clockTypes = ['static', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
const clockTypes = ['single_color', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
(document.getElementById('css-editor-clock-group') as HTMLElement).style.display = clockTypes.includes(type) ? '' : 'none';
|
||||
if (clockTypes.includes(type)) _populateClockDropdown();
|
||||
|
||||
@@ -726,16 +726,16 @@ function _ensureWeatherTempInfluenceWidget(): BindableScalarWidget {
|
||||
return _weatherTempInfluenceWidget;
|
||||
}
|
||||
|
||||
function _ensureStaticColorWidget(): BindableColorWidget {
|
||||
if (!_staticColorWidget) {
|
||||
_staticColorWidget = new BindableColorWidget({
|
||||
function _ensureSingleColorWidget(): BindableColorWidget {
|
||||
if (!_singleColorWidget) {
|
||||
_singleColorWidget = new BindableColorWidget({
|
||||
container: document.getElementById('css-editor-color-container')!,
|
||||
default: [255, 255, 255],
|
||||
valueSources: () => _cachedValueSources,
|
||||
idPrefix: 'css-editor-color',
|
||||
});
|
||||
}
|
||||
return _staticColorWidget;
|
||||
return _singleColorWidget;
|
||||
}
|
||||
|
||||
function _ensureEffectColorWidget(): BindableColorWidget {
|
||||
@@ -988,17 +988,17 @@ function _autoGenerateCSSName() {
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
const _typeHandlers: Record<string, { load: (...args: any[]) => any; reset: (...args: any[]) => any; getPayload: (name: any) => any }> = {
|
||||
static: {
|
||||
single_color: {
|
||||
load(css) {
|
||||
_ensureStaticColorWidget().setValue(css.color);
|
||||
_ensureSingleColorWidget().setValue(css.color);
|
||||
_loadAnimationState(css.animation);
|
||||
},
|
||||
reset() {
|
||||
_ensureStaticColorWidget().setValue([255, 255, 255]);
|
||||
_ensureSingleColorWidget().setValue([255, 255, 255]);
|
||||
_loadAnimationState(null);
|
||||
},
|
||||
getPayload(name) {
|
||||
return { name, color: _ensureStaticColorWidget().getValue(), animation: _getAnimationPayload() };
|
||||
return { name, color: _ensureSingleColorWidget().getValue(), animation: _getAnimationPayload() };
|
||||
},
|
||||
},
|
||||
gradient: {
|
||||
@@ -1417,7 +1417,7 @@ export function getCSSEditorPreviewPayload(sourceType: string): any {
|
||||
const payload = handler.getPayload('__preview__');
|
||||
if (!payload) return null;
|
||||
payload.source_type = sourceType;
|
||||
const clockTypes = ['static', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
const clockTypes = ['single_color', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
if (clockTypes.includes(sourceType)) {
|
||||
const clockEl = document.getElementById('css-editor-clock') as HTMLInputElement | null;
|
||||
if (clockEl && clockEl.value) payload.clock_id = clockEl.value;
|
||||
@@ -1559,6 +1559,8 @@ export function isCSSEditorDirty() { return cssEditorModal.isDirty(); }
|
||||
|
||||
export async function saveCSSEditor() {
|
||||
const cssId = (document.getElementById('css-editor-id') as HTMLInputElement).value;
|
||||
if (cssEditorModal.closeIfPristine(cssId)) return;
|
||||
|
||||
const name = (document.getElementById('css-editor-name') as HTMLInputElement).value.trim();
|
||||
const sourceType = (document.getElementById('css-editor-type') as HTMLInputElement).value;
|
||||
|
||||
@@ -1571,7 +1573,7 @@ export async function saveCSSEditor() {
|
||||
|
||||
payload.source_type = knownType ? sourceType : 'picture';
|
||||
|
||||
const clockTypes = ['static', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
const clockTypes = ['single_color', 'gradient', 'effect', 'daylight', 'candlelight', 'weather', 'math_wave'];
|
||||
if (clockTypes.includes(sourceType)) {
|
||||
payload.clock_id = (document.getElementById('css-editor-clock') as HTMLInputElement).value || null;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { openAuthedWs } from '../../core/ws-auth.ts';
|
||||
/* ── Preview config builder ───────────────────────────────────── */
|
||||
|
||||
const _PREVIEW_TYPES = new Set([
|
||||
'static', 'gradient', 'effect', 'daylight', 'candlelight', 'notification', 'audio', 'math_wave',
|
||||
'single_color', 'gradient', 'effect', 'daylight', 'candlelight', 'notification', 'audio', 'math_wave',
|
||||
'weather', 'game_event', 'api_input', 'mapped', 'composite', 'processed',
|
||||
]);
|
||||
|
||||
@@ -38,8 +38,8 @@ function _collectPreviewConfig() {
|
||||
const sourceType = (document.getElementById('css-editor-type') as HTMLInputElement).value;
|
||||
if (!_PREVIEW_TYPES.has(sourceType)) return null;
|
||||
let config: any;
|
||||
if (sourceType === 'static') {
|
||||
config = { source_type: 'static', color: hexToRgbArray((document.getElementById('css-editor-color') as HTMLInputElement).value), animation: _getAnimationPayload() };
|
||||
if (sourceType === 'single_color') {
|
||||
config = { source_type: 'single_color', color: hexToRgbArray((document.getElementById('css-editor-color') as HTMLInputElement).value), animation: _getAnimationPayload() };
|
||||
} else if (sourceType === 'gradient') {
|
||||
const stops = getGradientStops();
|
||||
if (stops.length < 2) return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ calibration, color correction, smoothing, and FPS.
|
||||
Current types:
|
||||
PictureColorStripSource — derives LED colors from a single PictureSource (simple 4-edge calibration)
|
||||
AdvancedPictureColorStripSource — line-based calibration across multiple PictureSources
|
||||
StaticColorStripSource — constant solid color fills all LEDs
|
||||
SingleColorStripSource — constant solid color fills all LEDs
|
||||
GradientColorStripSource — linear gradient across all LEDs from user-defined color stops
|
||||
AudioColorStripSource — audio-reactive visualization (spectrum, beat pulse, VU meter)
|
||||
ApiInputColorStripSource — receives raw LED colors from external clients via REST/WebSocket
|
||||
@@ -366,8 +366,8 @@ class AdvancedPictureColorStripSource(ColorStripSource):
|
||||
|
||||
|
||||
@dataclass
|
||||
class StaticColorStripSource(ColorStripSource):
|
||||
"""Color strip source that fills all LEDs with a single static color.
|
||||
class SingleColorStripSource(ColorStripSource):
|
||||
"""Color strip source that fills all LEDs with a single solid color.
|
||||
|
||||
No capture or processing -- the entire LED strip is set to one constant
|
||||
RGB color. Useful for solid-color accents or as a placeholder while
|
||||
@@ -384,11 +384,11 @@ class StaticColorStripSource(ColorStripSource):
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "StaticColorStripSource":
|
||||
def from_dict(cls, data: dict) -> "SingleColorStripSource":
|
||||
common = _parse_css_common(data)
|
||||
return cls(
|
||||
**common,
|
||||
source_type="static",
|
||||
source_type="single_color",
|
||||
color=BindableColor.from_raw(data.get("color"), default=[255, 255, 255]),
|
||||
animation=data.get("animation"),
|
||||
)
|
||||
@@ -412,7 +412,7 @@ class StaticColorStripSource(ColorStripSource):
|
||||
return cls(
|
||||
id=id,
|
||||
name=name,
|
||||
source_type="static",
|
||||
source_type="single_color",
|
||||
created_at=created_at,
|
||||
updated_at=updated_at,
|
||||
description=description,
|
||||
@@ -1823,7 +1823,10 @@ class MathWaveColorStripSource(ColorStripSource):
|
||||
_SOURCE_TYPE_MAP: Dict[str, Type[ColorStripSource]] = {
|
||||
"picture": PictureColorStripSource,
|
||||
"picture_advanced": AdvancedPictureColorStripSource,
|
||||
"static": StaticColorStripSource,
|
||||
"single_color": SingleColorStripSource,
|
||||
# Legacy alias: pre-rename rows used "static". Kept so old DBs deserialize;
|
||||
# ColorStripStore migrates the on-disk source_type to "single_color" on startup.
|
||||
"static": SingleColorStripSource,
|
||||
"gradient": GradientColorStripSource,
|
||||
"effect": EffectColorStripSource,
|
||||
"audio": AudioColorStripSource,
|
||||
|
||||
@@ -7,23 +7,23 @@ Tests creating, listing, updating, cloning, and deleting color strip sources.
|
||||
class TestColorStripSourceLifecycle:
|
||||
"""A user manages color strip sources for LED effects."""
|
||||
|
||||
def test_static_and_gradient_crud(self, client):
|
||||
# 1. Create a static color strip source
|
||||
def test_single_color_and_gradient_crud(self, client):
|
||||
# 1. Create a single-color strip source
|
||||
resp = client.post(
|
||||
"/api/v1/color-strip-sources",
|
||||
json={
|
||||
"name": "Red Static",
|
||||
"source_type": "static",
|
||||
"name": "Red Single",
|
||||
"source_type": "single_color",
|
||||
"color": [255, 0, 0],
|
||||
"led_count": 60,
|
||||
"tags": ["e2e", "static"],
|
||||
"tags": ["e2e", "single_color"],
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 201, f"Create static failed: {resp.text}"
|
||||
assert resp.status_code == 201, f"Create single_color failed: {resp.text}"
|
||||
static = resp.json()
|
||||
static_id = static["id"]
|
||||
assert static["name"] == "Red Static"
|
||||
assert static["source_type"] == "static"
|
||||
assert static["name"] == "Red Single"
|
||||
assert static["source_type"] == "single_color"
|
||||
assert static["color"] == [255, 0, 0]
|
||||
|
||||
# 2. Create a gradient color strip source
|
||||
@@ -58,7 +58,7 @@ class TestColorStripSourceLifecycle:
|
||||
# 4. Update the static source -- change color
|
||||
resp = client.put(
|
||||
f"/api/v1/color-strip-sources/{static_id}",
|
||||
json={"source_type": "static", "color": [0, 255, 0]},
|
||||
json={"source_type": "single_color", "color": [0, 255, 0]},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["color"] == [0, 255, 0]
|
||||
@@ -72,8 +72,8 @@ class TestColorStripSourceLifecycle:
|
||||
resp = client.post(
|
||||
"/api/v1/color-strip-sources",
|
||||
json={
|
||||
"name": "Cloned Static",
|
||||
"source_type": "static",
|
||||
"name": "Cloned Single",
|
||||
"source_type": "single_color",
|
||||
"color": [0, 255, 0],
|
||||
"led_count": 60,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ class TestColorStripSourceLifecycle:
|
||||
assert resp.status_code == 201
|
||||
clone_id = resp.json()["id"]
|
||||
assert clone_id != static_id
|
||||
assert resp.json()["name"] == "Cloned Static"
|
||||
assert resp.json()["name"] == "Cloned Single"
|
||||
|
||||
# 7. Delete all three
|
||||
for sid in [static_id, gradient_id, clone_id]:
|
||||
@@ -99,7 +99,7 @@ class TestColorStripSourceLifecycle:
|
||||
"/api/v1/color-strip-sources",
|
||||
json={
|
||||
"name": "Original Name",
|
||||
"source_type": "static",
|
||||
"source_type": "single_color",
|
||||
"color": [100, 100, 100],
|
||||
"led_count": 10,
|
||||
},
|
||||
@@ -108,7 +108,7 @@ class TestColorStripSourceLifecycle:
|
||||
|
||||
resp = client.put(
|
||||
f"/api/v1/color-strip-sources/{source_id}",
|
||||
json={"source_type": "static", "name": "New Name"},
|
||||
json={"source_type": "single_color", "name": "New Name"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["name"] == "New Name"
|
||||
@@ -125,7 +125,7 @@ class TestColorStripSourceLifecycle:
|
||||
"""Cannot create two sources with the same name."""
|
||||
payload = {
|
||||
"name": "Unique Name",
|
||||
"source_type": "static",
|
||||
"source_type": "single_color",
|
||||
"color": [0, 0, 0],
|
||||
"led_count": 10,
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ def store(tmp_db):
|
||||
|
||||
|
||||
def _create_static(store: ColorStripStore, name: str) -> str:
|
||||
"""Create a static color strip source, return its ID."""
|
||||
source = store.create_source(name=name, source_type="static", colors=[[255, 0, 0]])
|
||||
"""Create a single-color strip source, return its ID."""
|
||||
source = store.create_source(name=name, source_type="single_color", colors=[[255, 0, 0]])
|
||||
return source.id
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user