v2.11.1 (#721)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Mert Unsal <mertunsal1905@gmail.com> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: maximevoisin-pm <maxime.voisin@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
adb1ca74ce
commit
cf3f4ca58f
143 changed files with 3457 additions and 1351 deletions
0
tests/vscode_extension_promo/__init__.py
Normal file
0
tests/vscode_extension_promo/__init__.py
Normal file
17
tests/vscode_extension_promo/fake_repository.py
Normal file
17
tests/vscode_extension_promo/fake_repository.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from vibe.cli.vscode_extension_promo import (
|
||||
VscodeExtensionPromoRepository,
|
||||
VscodeExtensionPromoState,
|
||||
)
|
||||
|
||||
|
||||
class FakeVscodeExtensionPromoRepository(VscodeExtensionPromoRepository):
|
||||
def __init__(self, state: VscodeExtensionPromoState | None = None) -> None:
|
||||
self.state: VscodeExtensionPromoState | None = state
|
||||
|
||||
async def get(self) -> VscodeExtensionPromoState | None:
|
||||
return self.state
|
||||
|
||||
async def set(self, state: VscodeExtensionPromoState) -> None:
|
||||
self.state = state
|
||||
156
tests/vscode_extension_promo/test_ui_vscode_extension_promo.py
Normal file
156
tests/vscode_extension_promo/test_ui_vscode_extension_promo.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import build_test_vibe_app, build_test_vibe_config
|
||||
from tests.update_notifier.adapters.fake_update_cache_repository import (
|
||||
FakeUpdateCacheRepository,
|
||||
)
|
||||
from tests.update_notifier.adapters.fake_update_gateway import FakeUpdateGateway
|
||||
from tests.vscode_extension_promo.fake_repository import (
|
||||
FakeVscodeExtensionPromoRepository,
|
||||
)
|
||||
from vibe.cli.textual_ui.app import VibeApp
|
||||
from vibe.cli.textual_ui.widgets.messages import (
|
||||
VSCODE_EXTENSION_PROMO_WHATS_NEW_SUFFIX,
|
||||
VscodeExtensionPromoMessage,
|
||||
WhatsNewMessage,
|
||||
)
|
||||
from vibe.cli.update_notifier import UpdateCache
|
||||
from vibe.cli.vscode_extension_promo import (
|
||||
VscodeExtensionPromo,
|
||||
VscodeExtensionPromoState,
|
||||
)
|
||||
from vibe.core.config import VibeConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vibe_config() -> VibeConfig:
|
||||
return build_test_vibe_config(enable_update_checks=True)
|
||||
|
||||
|
||||
def _build_app(
|
||||
*,
|
||||
config: VibeConfig,
|
||||
promo: VscodeExtensionPromo | None,
|
||||
update_cache_repository: FakeUpdateCacheRepository,
|
||||
current_version: str = "1.0.0",
|
||||
) -> VibeApp:
|
||||
return build_test_vibe_app(
|
||||
config=config,
|
||||
update_notifier=FakeUpdateGateway(update=None),
|
||||
update_cache_repository=update_cache_repository,
|
||||
current_version=current_version,
|
||||
vscode_extension_promo=promo,
|
||||
)
|
||||
|
||||
|
||||
async def _wait_for(predicate: Callable[[], bool], pilot, timeout: float = 1.0) -> None:
|
||||
loop = asyncio.get_running_loop()
|
||||
deadline = loop.time() + timeout
|
||||
while loop.time() < deadline:
|
||||
if predicate():
|
||||
return
|
||||
await pilot.pause(0.05)
|
||||
pytest.fail("Condition not met within timeout")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _bypass_promo_gates() -> object:
|
||||
with (
|
||||
patch("vibe.cli.textual_ui.app._is_vscode_family_terminal", return_value=True),
|
||||
patch("vibe.cli.textual_ui.app.should_show_promo") as mocked_should_show,
|
||||
):
|
||||
mocked_should_show.side_effect = lambda state: (
|
||||
state is None or state.shown_count < 10
|
||||
)
|
||||
yield mocked_should_show
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promo_appears_as_standalone_chat_message_when_no_whats_new(
|
||||
vibe_config: VibeConfig, tmp_path: Path
|
||||
) -> None:
|
||||
repository = FakeVscodeExtensionPromoRepository()
|
||||
promo = VscodeExtensionPromo(repository=repository, initial_state=None)
|
||||
cache = UpdateCache(
|
||||
latest_version="1.0.0",
|
||||
stored_at_timestamp=int(time.time()),
|
||||
seen_whats_new_version="1.0.0",
|
||||
)
|
||||
app = _build_app(
|
||||
config=vibe_config,
|
||||
promo=promo,
|
||||
update_cache_repository=FakeUpdateCacheRepository(update_cache=cache),
|
||||
)
|
||||
|
||||
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause(0.3)
|
||||
await _wait_for(lambda: bool(app.query(VscodeExtensionPromoMessage)), pilot)
|
||||
|
||||
assert not app.query(WhatsNewMessage)
|
||||
assert repository.state == VscodeExtensionPromoState(shown_count=1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promo_is_merged_into_whats_new_message_when_both_shown(
|
||||
vibe_config: VibeConfig, tmp_path: Path
|
||||
) -> None:
|
||||
repository = FakeVscodeExtensionPromoRepository()
|
||||
promo = VscodeExtensionPromo(repository=repository, initial_state=None)
|
||||
cache = UpdateCache(
|
||||
latest_version="1.0.0",
|
||||
stored_at_timestamp=int(time.time()),
|
||||
seen_whats_new_version=None,
|
||||
)
|
||||
app = _build_app(
|
||||
config=vibe_config,
|
||||
promo=promo,
|
||||
update_cache_repository=FakeUpdateCacheRepository(update_cache=cache),
|
||||
)
|
||||
|
||||
whats_new_content = "# What's New\n\n- Feature 1"
|
||||
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
|
||||
(tmp_path / "whats_new.md").write_text(whats_new_content)
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause(0.3)
|
||||
await _wait_for(lambda: bool(app.query(WhatsNewMessage)), pilot)
|
||||
message = app.query_one(WhatsNewMessage)
|
||||
await _wait_for(
|
||||
lambda: repository.state == VscodeExtensionPromoState(shown_count=1),
|
||||
pilot,
|
||||
)
|
||||
|
||||
assert not app.query(VscodeExtensionPromoMessage)
|
||||
assert message._content.startswith(whats_new_content)
|
||||
assert message._content.endswith(VSCODE_EXTENSION_PROMO_WHATS_NEW_SUFFIX)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_promo_does_not_appear_when_no_promo_dependency_provided(
|
||||
vibe_config: VibeConfig, tmp_path: Path
|
||||
) -> None:
|
||||
cache = UpdateCache(
|
||||
latest_version="1.0.0",
|
||||
stored_at_timestamp=int(time.time()),
|
||||
seen_whats_new_version="1.0.0",
|
||||
)
|
||||
app = _build_app(
|
||||
config=vibe_config,
|
||||
promo=None,
|
||||
update_cache_repository=FakeUpdateCacheRepository(update_cache=cache),
|
||||
)
|
||||
|
||||
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause(0.4)
|
||||
|
||||
assert not app.query(VscodeExtensionPromoMessage)
|
||||
assert not app.query(WhatsNewMessage)
|
||||
74
tests/vscode_extension_promo/test_vscode_extension_promo.py
Normal file
74
tests/vscode_extension_promo/test_vscode_extension_promo.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
|
||||
import pytest
|
||||
import tomli_w
|
||||
|
||||
from vibe.cli.vscode_extension_promo import (
|
||||
MAX_SHOWN_COUNT,
|
||||
PROMO_START,
|
||||
FileSystemVscodeExtensionPromoRepository,
|
||||
VscodeExtensionPromoState,
|
||||
should_show_promo,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repository_roundtrip_persists_shown_count(tmp_path: Path) -> None:
|
||||
repository = FileSystemVscodeExtensionPromoRepository(base_path=tmp_path)
|
||||
|
||||
await repository.set(VscodeExtensionPromoState(shown_count=4))
|
||||
|
||||
assert await repository.get() == VscodeExtensionPromoState(shown_count=4)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repository_set_preserves_unrelated_cache_sections(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
with (tmp_path / "cache.toml").open("wb") as f:
|
||||
tomli_w.dump({"update_cache": {"latest_version": "2.0.0"}}, f)
|
||||
repository = FileSystemVscodeExtensionPromoRepository(base_path=tmp_path)
|
||||
|
||||
await repository.set(VscodeExtensionPromoState(shown_count=1))
|
||||
|
||||
with (tmp_path / "cache.toml").open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
assert data["update_cache"]["latest_version"] == "2.0.0"
|
||||
assert data["vscode_extension_promo"]["shown_count"] == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"state,expected",
|
||||
[
|
||||
(None, True),
|
||||
(VscodeExtensionPromoState(shown_count=0), True),
|
||||
(VscodeExtensionPromoState(shown_count=MAX_SHOWN_COUNT - 1), True),
|
||||
(VscodeExtensionPromoState(shown_count=MAX_SHOWN_COUNT), False),
|
||||
(VscodeExtensionPromoState(shown_count=MAX_SHOWN_COUNT + 5), False),
|
||||
],
|
||||
)
|
||||
def test_should_show_promo_respects_threshold(
|
||||
state: VscodeExtensionPromoState | None, expected: bool
|
||||
) -> None:
|
||||
assert should_show_promo(state, now=PROMO_START) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"now,expected",
|
||||
[
|
||||
(PROMO_START - timedelta(seconds=1), False),
|
||||
(PROMO_START - timedelta(days=365), False),
|
||||
(PROMO_START, True),
|
||||
(PROMO_START + timedelta(seconds=1), True),
|
||||
],
|
||||
)
|
||||
def test_should_show_promo_gated_by_start_date(now: datetime, expected: bool) -> None:
|
||||
assert should_show_promo(None, now=now) is expected
|
||||
|
||||
|
||||
def test_promo_start_is_2026_05_28_16_utc() -> None:
|
||||
assert PROMO_START == datetime(2026, 5, 28, 16, 0, tzinfo=UTC)
|
||||
Loading…
Add table
Add a link
Reference in a new issue